Merge branch 'develop'

Conflicts:
	dpaste/urls/dpaste.py
	dpaste/views.py
This commit is contained in:
Martin Mahner 2013-09-27 17:47:09 +02:00
commit 0e251a0392
16 changed files with 255 additions and 637 deletions

View file

@ -1,24 +1,23 @@
import datetime
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from dpaste.models import Snippet
from dpaste.highlight import LEXER_LIST, LEXER_DEFAULT
import datetime
#===============================================================================
# Snippet Form and Handling
#===============================================================================
EXPIRE_CHOICES = (
(3600, _(u'In one hour')),
(3600 * 24 * 7, _(u'In one week')),
(3600 * 24 * 30, _(u'In one month')),
)
EXPIRE_DEFAULT = 3600 * 24 * 30
EXPIRE_DEFAULT = EXPIRE_CHOICES[2][0]
MAX_CONTENT_LENGTH = getattr(settings, 'DPASTE_MAX_CONTENT_LENGTH', 250*1024*1024)
MAX_SNIPPETS_PER_USER = getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 15)
\
class SnippetForm(forms.ModelForm):
content = forms.CharField(
label=_('Content'),
@ -29,7 +28,7 @@ class SnippetForm(forms.ModelForm):
lexer = forms.ChoiceField(
label=_(u'Lexer'),
initial=LEXER_DEFAULT,
widget=forms.TextInput,
choices=LEXER_LIST,
)
expire_options = forms.ChoiceField(
@ -55,35 +54,23 @@ class SnippetForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
super(SnippetForm, self).__init__(*args, **kwargs)
self.request = request
self.fields['lexer'].choices = LEXER_LIST
self.fields['lexer'].widget.attrs = {
'autocomplete': 'off',
'data-provide': 'typeahead',
'data-source': '["%s"]' % '","'.join(dict(LEXER_LIST).keys())
}
# Set the recently used lexer if we have any
session_lexer = self.request.session.get('lexer')
if session_lexer and session_lexer in dict(LEXER_LIST).keys():
self.fields['lexer'].initial = session_lexer
def clean_lexer(self):
lexer = self.cleaned_data.get('lexer')
if not lexer:
return LEXER_DEFAULT
lexer = dict(LEXER_LIST).get(lexer, LEXER_DEFAULT)
return lexer
def clean_content(self):
return self.cleaned_data.get('content', '').strip()
def clean(self):
# The `title` field is a hidden honeypot field. If its filled,
# this is likely spam.
if self.cleaned_data.get('title'):
raise forms.ValidationError('This snippet was identified as Spam.')
return self.cleaned_data
def save(self, parent=None, *args, **kwargs):
# Set parent snippet
if parent:
self.instance.parent = parent
@ -97,7 +84,7 @@ class SnippetForm(forms.ModelForm):
# Add the snippet to the user session list
if self.request.session.get('snippet_list', False):
if len(self.request.session['snippet_list']) >= getattr(settings, 'MAX_SNIPPETS_PER_USER', 10):
if len(self.request.session['snippet_list']) >= MAX_SNIPPETS_PER_USER:
self.request.session['snippet_list'].pop(0)
self.request.session['snippet_list'] += [self.instance.pk]
else:
@ -106,4 +93,4 @@ class SnippetForm(forms.ModelForm):
# Save the lexer in the session so we can use it later again
self.request.session['lexer'] = self.cleaned_data['lexer']
return self.request, self.instance
return self.instance

View file

@ -1,23 +1,102 @@
from pygments import highlight
from pygments.lexers import *
from pygments.lexers import get_all_lexers
from pygments.formatters import HtmlFormatter
from django.conf import settings
from django.utils.html import escape
"""
# Get a list of all lexer, and then remove all lexer which have '-' or '+'
# or 'with' in the name. Those are too specific and never used. This produces a
# tuple list of [(lexer, Lexer Display Name) ...] lexers.
from pygments.lexers import get_all_lexers
ALL_LEXER = set([(i[1][0], i[0]) for i in get_all_lexers()])
LEXER_LIST = [l for l in ALL_LEXER if not (
'-' in l[0]
or '+' in l[0]
or '+' in l[1]
or 'with' in l[1].lower()
or ' ' in l[1]
or l[0] in IGNORE_LEXER
)]
LEXER_LIST = sorted(LEXER_LIST)
"""
import logging
logger = logging.getLogger(__name__)
# The list of lexers. Its not worth to autogenerate this. See above how to
# retrieve this.
LEXER_LIST = getattr(settings, 'DPASTE_LEXER_LIST', (
('text', 'Text'),
('text', '----------'),
('apacheconf', 'ApacheConf'),
('applescript', 'AppleScript'),
('as', 'ActionScript'),
('bash', 'Bash'),
('bbcode', 'BBCode'),
('c', 'C'),
('clojure', 'Clojure'),
('cobol', 'COBOL'),
('css', 'CSS'),
('cuda', 'CUDA'),
('dart', 'Dart'),
('delphi', 'Delphi'),
('diff', 'Diff'),
('django', 'Django'),
('erlang', 'Erlang'),
('fortran', 'Fortran'),
('go', 'Go'),
('groovy', 'Groovy'),
('haml', 'Haml'),
('haskell', 'Haskell'),
('html', 'HTML'),
('http', 'HTTP'),
('ini', 'INI'),
('java', 'Java'),
('js', 'JavaScript'),
('json', 'JSON'),
('lua', 'Lua'),
('make', 'Makefile'),
('mako', 'Mako'),
('mason', 'Mason'),
('matlab', 'Matlab'),
('modula2', 'Modula'),
('monkey', 'Monkey'),
('mysql', 'MySQL'),
('numpy', 'NumPy'),
('ocaml', 'OCaml'),
('perl', 'Perl'),
('php', 'PHP'),
('postscript', 'PostScript'),
('powershell', 'PowerShell'),
('prolog', 'Prolog'),
('properties', 'Properties'),
('puppet', 'Puppet'),
('python', 'Python'),
('rb', 'Ruby'),
('rst', 'reStructuredText'),
('rust', 'Rust'),
('sass', 'Sass'),
('scala', 'Scala'),
('scheme', 'Scheme'),
('scilab', 'Scilab'),
('scss', 'SCSS'),
('smalltalk', 'Smalltalk'),
('smarty', 'Smarty'),
('sql', 'SQL'),
('tcl', 'Tcl'),
('tcsh', 'Tcsh'),
('tex', 'TeX'),
('vb.net', 'VB.net'),
('vim', 'VimL'),
('xml', 'XML'),
('xquery', 'XQuery'),
('xslt', 'XSLT'),
('yaml', 'YAML'),
))
# Python 3: python3
LEXER_LIST = sorted([(i[0], i[0]) for i in get_all_lexers() if not (
'+' in i[0] or
'with' in i[0].lower() or
i[0].islower()
)])
LEXER_LIST_NAME = dict([(i[0], i[1][0]) for i in get_all_lexers()])
# The default lexer is python
LEXER_DEFAULT = getattr(settings, 'DPASTE_LEXER_DEFAULT', 'python')
# Lexers which have wordwrap enabled by default
LEXER_WORDWRAP = getattr(settings, 'DPASTE_LEXER_WORDWRAP', ('text', 'rst'))
LEXER_DEFAULT = 'Python'
LEXER_WORDWRAP = ('text', 'rst')
class NakedHtmlFormatter(HtmlFormatter):
def wrap(self, source, outfile):
@ -28,25 +107,6 @@ class NakedHtmlFormatter(HtmlFormatter):
yield i, t
def pygmentize(code_string, lexer_name=LEXER_DEFAULT):
lexer_name = LEXER_LIST_NAME.get(lexer_name, None)
try:
if lexer_name:
lexer = get_lexer_by_name(lexer_name)
else:
raise Exception
except:
try:
lexer = guess_lexer(code_string)
except:
lexer = PythonLexer()
try:
return highlight(code_string, lexer, NakedHtmlFormatter())
except:
return escape(code_string)
def guess_code_lexer(code_string, default_lexer='unknown'):
try:
return guess_lexer(code_string).name
except ValueError:
return default_lexer
lexer = lexer_name and get_lexer_by_name(lexer_name) \
or PythonLexer()
return highlight(code_string, lexer, NakedHtmlFormatter())

View file

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'Snippet.content_highlighted'
db.delete_column(u'dpaste_snippet', 'content_highlighted')
def backwards(self, orm):
# Adding field 'Snippet.content_highlighted'
db.add_column(u'dpaste_snippet', 'content_highlighted',
self.gf('django.db.models.fields.TextField')(default='', blank=True),
keep_default=False)
models = {
u'dpaste.snippet': {
'Meta': {'ordering': "('-published',)", 'object_name': 'Snippet'},
'content': ('django.db.models.fields.TextField', [], {}),
'expires': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'lexer': ('django.db.models.fields.CharField', [], {'default': "'Python'", 'max_length': '30'}),
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': u"orm['dpaste.Snippet']"}),
'published': ('django.db.models.fields.DateTimeField', [], {'blank': 'True'}),
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
'secret_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
}
}
complete_apps = ['dpaste']

View file

@ -7,16 +7,15 @@ from django.utils.translation import ugettext_lazy as _
from dpaste.highlight import LEXER_DEFAULT
t = 'abcdefghijkmnopqrstuvwwxyzABCDEFGHIJKLOMNOPQRSTUVWXYZ1234567890'
def generate_secret_id(length=5):
def generate_secret_id(length=4):
return ''.join([random.choice(t) for i in range(length)])
class Snippet(models.Model):
secret_id = models.CharField(_(u'Secret ID'), max_length=255, blank=True)
content = models.TextField(_(u'Content'), )
content_highlighted = models.TextField(_(u'Highlighted Content'), blank=True)
lexer = models.CharField(_(u'Lexer'), max_length=30, default=LEXER_DEFAULT)
published = models.DateTimeField(_(u'Published'), blank=True)
expires = models.DateTimeField(_(u'Expires'), blank=True, help_text='asdf')
expires = models.DateTimeField(_(u'Expires'), blank=True)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
class Meta:
@ -27,7 +26,7 @@ class Snippet(models.Model):
return len(self.content.splitlines())
def content_splitted(self):
return self.content_highlighted.splitlines()
return self.content.splitlines()
@property
def is_single(self):
@ -37,13 +36,12 @@ class Snippet(models.Model):
if not self.pk:
self.published = datetime.datetime.now()
self.secret_id = generate_secret_id()
self.content_highlighted = self.content
super(Snippet, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('snippet_details', kwargs={'snippet_id': self.secret_id})
def __unicode__(self):
return '%s' % self.secret_id
return self.secret_id
mptt.register(Snippet, order_insertion_by=['content'])

View file

@ -19,4 +19,4 @@ DATABASES = {
SECRET_KEY = 'changeme'
EMAIL_BACKEND = 'dpaste.smtp.EmailBackend'
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

View file

@ -1,57 +0,0 @@
"""sendmail email backend class."""
import threading
from django.core.mail.backends.base import BaseEmailBackend
from subprocess import Popen, PIPE
class EmailBackend(BaseEmailBackend):
"""
EmailBackend that uses a local 'sendmail' binary instead of a local
SMTP daemon.
"""
def __init__(self, fail_silently=False, **kwargs):
super(EmailBackend, self).__init__(fail_silently=fail_silently)
self._lock = threading.RLock()
def open(self):
return True
def close(self):
pass
def send_messages(self, email_messages):
"""
Sends one or more EmailMessage objects and returns the number of email
messages sent.
"""
if not email_messages:
return
self._lock.acquire()
try:
num_sent = 0
for message in email_messages:
sent = self._send(message)
if sent:
num_sent += 1
finally:
self._lock.release()
return num_sent
def _send(self, email_message):
"""A helper method that does the actual sending."""
if not email_message.recipients():
return False
try:
ps = Popen(["sendmail"]+list(email_message.recipients()), \
stdin=PIPE)
ps.stdin.write(email_message.message().as_string())
ps.stdin.flush()
ps.stdin.close()
return not ps.wait()
except:
if not self.fail_silently:
raise
return False
return True

View file

@ -1,404 +0,0 @@
body{
margin: 0;
padding: 0;
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
}
a:link,
a:visited{
color: #3D813A;
text-decoration: none;
}
a:hover{
color: #52AA4D;
text-decoration: underline;
}
p.hint{
color: #333;
margin-top: 30px;
}
p.hint em{
color: black;
background-color: #c9f8b4;
display: inline-block;
padding: 2px 3px;
font-style: normal;
}
hr.clear{
clear: both;
border: none;
margin: 0;
padding: 0;
height: 0;
overflow: hidden;
font-size: 0;
line-height: 0;
visibility: hidden;
}
div.success{
background-color: green;
color: White;
margin: 10px 0;
padding: 10px 20px;
}
div.success a{
color: White;
text-decoration: underline;
}
div.hint{
padding: 5px 20px;
background-color: #F7F1C9;
margin: 20px 0;
}
/* *******************************************
* Header
******************************************* */
#header{
background-color: #D6F1B7;
border-bottom: 1px solid #C6EB9A;
padding: 10px 20px;
font-size: 14px;
}
#header span.new_snippet{
float: right;
}
#header h1{
margin: 0;
color: #555555;
font-size: 14px;
}
#header h1 span.date{
color: gray;
color: #666;
padding-left: 15px;
}
#header a:link,
#header a:visited{
text-decoration: none;
color: #333;
font-weight: Bold;
}
#header a:hover{
text-decoration: underline;
}
/* *******************************************
* Content
******************************************* */
#content{
padding: 0 20px;
margin: 0;
width: 70%;
float: left;
}
#content h2{
font-size: 1.3em;
line-height: 1.6em;
}
#content h2.divider{
font-size: 1em;
padding: 5px 20px;
background-color: #f8f8f8;
margin: 40px 0 20px 0;
}
#content h2 span{
font-weight: normal;
}
div.accordion h2{
cursor: pointer;
color: #3D813A;
}
div.accordion h2:hover{
text-decoration: underline;
}
/* *******************************************
* Snippet table
******************************************* */
div.snippet{
overflow: auto;
}
div.snippet-options{
float: right;
font-size: 0.9em;
margin-top: 5px;
}
div.snippet table{
margin: 0;
padding: 0;
border-collapse: collapse;
}
div.snippet table td{
margin: 0;
padding: 0 4px;
vertical-align: top;
}
div.snippet table th{
border-right: 1px solid #ccc;
vertical-align: top;
}
div.snippet table th a{
display: block;
text-decoration: none;
color: #888;
text-align: right;
padding: 0 4px 0 18px;
}
/* *******************************************
* Form
******************************************* */
form.snippetform ol{
margin: 0;
padding: 0;
list-style: none;
}
form.snippetform ol li{
margin: 0;
padding: 5px 10px;
border-bottom: 1px solid #EEE;
clear: left;
}
form.snippetform label{
width: 125px;
display: inline-block;
}
form.snippetform #id_content{
width: 80%;
height: 320px;
font-family: monospace;
font-size: 0.9em;
}
form.snippetform #id_author,
form.snippetform #id_title{
width: 60%;
opacity: 0.7;
}
form.snippetform li.submit input{
margin-left: 125px;
}
form.snippetform ul.errorlist,
form.snippetform ul.errorlist li{
margin: 0;
padding: 0;
list-style: none;
color: #c00;
font-weight: bold;
border: none;
}
form.snippetform ul.errorlist li{
padding: 10px 0 5px 0;
}
/* *******************************************
* History + Tree
******************************************* */
#sidebar{
padding: 0 20px 0 10px;
margin: 20px 0 0 0;
float: right;
width: 20%;
overflow: auto;
border-left: 1px solid #DDD;
}
#sidebar h2{
font-size: 1em;
border-bottom: 1px solid #DDD;
color: #888;
margin-top: 0;
text-transform: uppercase;
width: auto !important;
}
div.tree{
margin: 0 0 15px 0;
line-height: 1.8em;
}
div.tree ul,
div.tree ul li{
margin: 0;
padding: 0;
list-style: none;
}
div.tree ul li{
clear: both;
}
div.tree ul li div{
border-bottom: 1px solid #EEE;
}
div.tree span.diff{
float: right;
}
div.tree strong{
color: #111;
font-weight: normal;
}
div.tree ul li li{
padding-left: 0;
margin-left: 15px;
color: #ccc;
list-style: circle;
}
div.tree div.submit{
margin: 8px 0 0 0;
text-align: right;
}
div.tree div.submit input{
font-size: 0.8em;
}
/* *******************************************
* Footer
******************************************* */
#footer{
position: fixed;
right: 1em;
bottom: 1em;
}
#footer form.setlang{
display: inline;
padding-right: 15px;
}
#footer form.setlang input,
#footer form.setlang select{
font-size: 0.8em;
}
#footer a:link,
#footer a:visited{
background-color: #D6F1B7;
color: #555;
text-decoration: none;
padding: 3px 6px;
}
#footer a:hover,
#footer a:active{
background-color: #D6F1B7;
color: #000;
text-decoration: none;
}
/* *******************************************
* Pygments
******************************************* */
pre.code {
font-family: "Bitstream Vera Sans Mono", Monaco, Consolas, monospace;
font-size: 12px;
line-height: 17px;
margin: 0;
padding: 0;
}
pre.code div.line:hover{
background-color: #FFFFE6;
}
pre.code div.line.marked,
pre.code div.line.marked *{
background-color: #BAE688 !important;
}
.code .c { color: #999988; font-style: italic } /* Comment */
/* .code .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.code .k { font-weight: bold } /* Keyword */
.code .o { font-weight: bold } /* Operator */
.code .cm { color: #999988; font-style: italic } /* Comment.Multiline */
.code .cp { color: #999999; font-weight: bold } /* Comment..codeproc */
.code .c1 { color: #999988; font-style: italic } /* Comment.Single */
.code .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.code .ge { font-style: italic } /* Generic.Emph */
.code .gr { color: #aa0000 } /* Generic.Error */
.code .gh { color: #999999 } /* Generic.Heading */
.code .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.code .go { color: #888888 } /* Generic.Output */
.code .gp { color: #555555 } /* Generic.Prompt */
.code .gs { font-weight: bold } /* Generic.Strong */
.code .gu { color: #aaaaaa } /* Generic.Subheading */
.code .gt { color: #aa0000 } /* Generic.Traceback */
.code .kc { font-weight: bold } /* Keyword.Constant */
.code .kd { font-weight: bold } /* Keyword.Declaration */
.code .kp { font-weight: bold } /* Keyword.Pseudo */
.code .kr { font-weight: bold } /* Keyword.Reserved */
.code .kt { color: #445588; font-weight: bold } /* Keyword.Type */
.code .m { color: #009999 } /* Literal.Number */
.code .s { color: #bb8844 } /* Literal.String */
.code .na { color: #008080 } /* Name.Attribute */
.code .nb { color: #999999 } /* Name.Builtin */
.code .nc { color: #445588; font-weight: bold } /* Name.Class */
.code .no { color: #ff99ff } /* Name.Constant */
.code .ni { color: #800080 } /* Name.Entity */
.code .ne { color: #990000; font-weight: bold } /* Name.Exception */
.code .nf { color: #990000; font-weight: bold } /* Name.Function */
.code .nn { color: #555555 } /* Name.Namespace */
.code .nt { color: #000080 } /* Name.Tag */
.code .nv { color: purple } /* Name.Variable */
.code .ow { font-weight: bold } /* Operator.Word */
.code .mf { color: #009999 } /* Literal.Number.Float */
.code .mh { color: #009999 } /* Literal.Number.Hex */
.code .mi { color: #009999 } /* Literal.Number.Integer */
.code .mo { color: #009999 } /* Literal.Number.Oct */
.code .sb { color: #bb8844 } /* Literal.String.Backtick */
.code .sc { color: #bb8844 } /* Literal.String.Char */
.code .sd { color: #bb8844 } /* Literal.String.Doc */
.code .s2 { color: #bb8844 } /* Literal.String.Double */
.code .se { color: #bb8844 } /* Literal.String.Escape */
.code .sh { color: #bb8844 } /* Literal.String.Heredoc */
.code .si { color: #bb8844 } /* Literal.String.Interpol */
.code .sx { color: #bb8844 } /* Literal.String.Other */
.code .sr { color: #808000 } /* Literal.String.Regex */
.code .s1 { color: #bb8844 } /* Literal.String.Single */
.code .ss { color: #bb8844 } /* Literal.String.Symbol */
.code .bp { color: #999999 } /* Name.Builtin.Pseudo */
.code .vc { color: #ff99ff } /* Name.Variable.Class */
.code .vg { color: #ff99ff } /* Name.Variable.Global */
.code .vi { color: #ff99ff } /* Name.Variable.Instance */
.code .il { color: #009999 } /* Literal.Number.Integer.Long */

7
dpaste/static/dpaste/typeahead.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -28,33 +28,6 @@
{% block script_footer %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"></script>
<script src="{% static "dpaste/bootstrap/js/bootstrap.min.js" %}"></script>
<script>
jQuery(function($) {
var lexerReq;
$('#guess_lexer_btn').click(function() {
// Cancel previous request if it is still pending
if (lexerReq) {
lexerReq.abort();
}
lexerReq = $.getJSON('{% url "snippet_guess_lexer" %}', {
codestring: $('#id_content').val()
}).done(function(data) {
if (data.lexer === 'unknown') {
$('#guess_lexer_btn').css('color', 'red');
} else {
$('#id_lexer').val(data.lexer);
$('#guess_lexer_btn').css('color', 'inherit');
}
}).complete(function() {
lexerReq = null;
});
});
$('.autofocus input:text, .autofocus textarea').first().focus();
});
</script>
{% endblock %}
</body>

View file

@ -81,15 +81,15 @@
{{ block.super }}
<script>
jQuery(function($) {
var diffReq;
$('.snippet-reply-hidden').click(function(e) {
$(this).removeClass('snippet-reply-hidden');
});
/**
* Diff Ajax Call
*/
/* ------------------------------------------------------------------------
Diff Ajax Call
------------------------------------------------------------------------ */
var diffReq;
$('.snippet-diff-trigger').click(function(e) {
e.preventDefault();
$('#snippet-diff').slideDown('fast');
@ -134,10 +134,9 @@ jQuery(function($) {
}
}
/**
* Line Highlighting
*/
/* ------------------------------------------------------------------------
Line Highlighting
------------------------------------------------------------------------ */
if (curLine.substring(0, 2) === '#L') {
hashlist = curLine.substring(2).split(',');
if (hashlist.length > 0 && hashlist[0] !== '') {

View file

@ -19,7 +19,6 @@
{% if snippet_form.lexer.errors %}control-group error{% endif %}">
<div class="input-append">
{{ snippet_form.lexer }}
<button class="btn" id="guess_lexer_btn" type="button">{% trans "Guess lexer" %}</button>
</div>
{% for error in snippet_form.lexer.errors %}
<span class="help-inline">{{ error }}</span>
@ -42,4 +41,4 @@
{% endif %}
<input tabindex="0" type="submit"class="btn btn-primary" value="{% trans "Paste it" %}">
</div>
</form>
</form>

View file

@ -1,4 +1,5 @@
{% extends "dpaste/base.html" %}
{% load i18n %}
{% load dpaste_tags %}

View file

@ -18,11 +18,6 @@ class SnippetAPITestCase(TestCase):
def test_empty(self):
"""
The browser sent a content field but with no data.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
ALL tests fail due to a Piston bug:
https://bitbucket.org/jespern/django-piston/issue/221/attributeerror-httpresponseservererror
"""
data = {}

View file

@ -2,11 +2,11 @@ from django.conf.urls.defaults import url, patterns
urlpatterns = patterns('dpaste.views',
url(r'^$', 'snippet_new', name='snippet_new'),
url(r'^guess/$', 'guess_lexer', name='snippet_guess_lexer'),
url(r'^diff/$', 'snippet_diff', name='snippet_diff'),
url(r'^history/$', 'snippet_history', name='snippet_history'),
url(r'^delete/$', 'snippet_delete', name='snippet_delete'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]+)/$', 'snippet_details', name='snippet_details'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]+)/?$', 'snippet_details', name='snippet_details'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]+)/delete/$', 'snippet_delete', name='snippet_delete'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]+)/gist/$', 'snippet_gist', name='snippet_gist'),
url(r'^(?P<snippet_id>[a-zA-Z0-9]+)/raw/$', 'snippet_details', {'template_name': 'dpaste/snippet_details_raw.html', 'is_raw': True}, name='snippet_details_raw'),

View file

@ -2,45 +2,36 @@ import datetime
import difflib
import requests
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.shortcuts import (render_to_response, get_object_or_404,
get_list_or_404)
from django.template.context import RequestContext
from django.http import (Http404, HttpResponseRedirect, HttpResponseBadRequest,
HttpResponse, HttpResponseForbidden)
HttpResponse, HttpResponseForbidden)
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.utils import simplejson
from django.db.models import Count
from django.views.defaults import (page_not_found as django_page_not_found,
server_error as django_server_error)
server_error as django_server_error)
from dpaste.forms import SnippetForm
from dpaste.models import Snippet
from dpaste.highlight import guess_code_lexer, \
LEXER_WORDWRAP, LEXER_LIST
from dpaste.highlight import LEXER_WORDWRAP, LEXER_LIST
def about(request, template_name='dpaste/about.html'):
template_context = {
'total': Snippet.objects.count(),
'stats': Snippet.objects.values('lexer').annotate(
count=Count('lexer')).order_by('-count')[:5],
}
return render_to_response(
template_name,
template_context,
RequestContext(request)
)
# -----------------------------------------------------------------------------
# Snippet Handling
# -----------------------------------------------------------------------------
def snippet_new(request, template_name='dpaste/snippet_new.html'):
"""
Create a new snippet.
"""
if request.method == "POST":
snippet_form = SnippetForm(data=request.POST, request=request)
if snippet_form.is_valid():
request, new_snippet = snippet_form.save()
new_snippet = snippet_form.save()
url = new_snippet.get_absolute_url()
return HttpResponseRedirect(url)
else:
@ -48,6 +39,7 @@ def snippet_new(request, template_name='dpaste/snippet_new.html'):
template_context = {
'snippet_form': snippet_form,
'lexer_list': LEXER_LIST,
'is_new': True,
}
@ -58,25 +50,11 @@ def snippet_new(request, template_name='dpaste/snippet_new.html'):
)
def snippet_api(request, enclose_quotes=True):
content = request.POST.get('content', '').strip()
if not content:
return HttpResponseBadRequest()
s = Snippet.objects.create(
content=content,
expires=datetime.datetime.now()+datetime.timedelta(seconds=60*60*24*30)
)
s.save()
response = 'http://dpaste.de%s' % s.get_absolute_url()
if enclose_quotes:
return HttpResponse('"%s"' % response)
return HttpResponse(response)
def snippet_details(request, snippet_id, template_name='dpaste/snippet_details.html', is_raw=False):
"""
Details list view of a snippet. Handles the actual view, reply and
tree/diff view.
"""
try:
snippet = Snippet.objects.get(secret_id=snippet_id)
except MultipleObjectsReturned:
@ -98,7 +76,7 @@ def snippet_details(request, snippet_id, template_name='dpaste/snippet_details.h
if request.method == "POST":
snippet_form = SnippetForm(data=request.POST, request=request, initial=new_snippet_initial)
if snippet_form.is_valid():
request, new_snippet = snippet_form.save(parent=snippet)
new_snippet = snippet_form.save(parent=snippet)
url = new_snippet.get_absolute_url()
return HttpResponseRedirect(url)
else:
@ -125,34 +103,31 @@ def snippet_details(request, snippet_id, template_name='dpaste/snippet_details.h
else:
return response
def snippet_delete(request, snippet_id=None):
snippet_id = snippet_id or request.POST.get('snippet_id')
if not snippet_id:
return HttpResponseBadRequest('No snippet given!')
def snippet_delete(request, snippet_id):
"""
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 = get_object_or_404(Snippet, secret_id=snippet_id)
"""
Anybody can delete anybodys snippets now.
try:
snippet_list = request.session['snippet_list']
except KeyError:
return HttpResponseForbidden('You have no recent snippet list, cookie error?')
if not snippet.pk in snippet_list:
return HttpResponseForbidden('That\'s not your snippet!')
"""
snippet.delete()
return HttpResponseRedirect(reverse('snippet_new') + '?delete=1')
def snippet_history(request, template_name='dpaste/snippet_list.html'):
def snippet_history(request, template_name='dpaste/snippet_list.html'):
"""
Display the last `n` snippets created by this user (and saved in his
session).
"""
try:
snippet_list = get_list_or_404(Snippet, pk__in=request.session.get('snippet_list', None))
except ValueError:
snippet_list = None
template_context = {
'snippets_max': getattr(settings, 'MAX_SNIPPETS_PER_USER', 10),
'snippets_max': getattr(settings, 'DPASTE_MAX_SNIPPETS_PER_USER', 10),
'snippet_list': snippet_list,
}
@ -164,7 +139,9 @@ def snippet_history(request, template_name='dpaste/snippet_list.html'):
def snippet_diff(request, template_name='dpaste/snippet_diff.html'):
"""
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:
@ -207,6 +184,7 @@ def snippet_diff(request, template_name='dpaste/snippet_diff.html'):
RequestContext(request)
)
def snippet_gist(request, snippet_id):
"""
Put a snippet on Github Gist.
@ -235,15 +213,58 @@ def snippet_gist(request, snippet_id):
return HttpResponseRedirect(gist_url)
# -----------------------------------------------------------------------------
# Static pages
# -----------------------------------------------------------------------------
def guess_lexer(request):
code_string = request.GET.get('codestring', False)
response = simplejson.dumps({'lexer': guess_code_lexer(code_string)})
def about(request, template_name='dpaste/about.html'):
"""
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],
}
return render_to_response(
template_name,
template_context,
RequestContext(request)
)
# -----------------------------------------------------------------------------
# API Handling
# -----------------------------------------------------------------------------
def snippet_api(request, enclose_quotes=True):
content = request.POST.get('content', '').strip()
if not content:
return HttpResponseBadRequest()
s = Snippet.objects.create(
content=content,
expires=datetime.datetime.now()+datetime.timedelta(seconds=60*60*24*30)
)
s.save()
response = 'http://dpaste.de%s' % s.get_absolute_url()
if enclose_quotes:
return HttpResponse('"%s"' % response)
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, template_name='dpaste/404.html'):
return django_page_not_found(request, template_name)
def server_error(request, template_name='dpaste/500.html'):
return django_server_error(request, template_name)

View file

@ -1,9 +1,9 @@
django==1.5.1
django-mptt==0.4.2
django==1.5.2
django-mptt==0.6.0
pygments==1.6
south==0.7.6
south==0.8.2
requests==1.2.3
# Deployment specific
##mysql-python==1.2.4
gunicorn==0.17.2
#mysql-python==1.2.4
gunicorn==17.5