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 import forms
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from dpaste.models import Snippet from dpaste.models import Snippet
from dpaste.highlight import LEXER_LIST, LEXER_DEFAULT from dpaste.highlight import LEXER_LIST, LEXER_DEFAULT
import datetime
#===============================================================================
# Snippet Form and Handling
#===============================================================================
EXPIRE_CHOICES = ( EXPIRE_CHOICES = (
(3600, _(u'In one hour')), (3600, _(u'In one hour')),
(3600 * 24 * 7, _(u'In one week')), (3600 * 24 * 7, _(u'In one week')),
(3600 * 24 * 30, _(u'In one month')), (3600 * 24 * 30, _(u'In one month')),
) )
EXPIRE_DEFAULT = EXPIRE_CHOICES[2][0]
EXPIRE_DEFAULT = 3600 * 24 * 30
MAX_CONTENT_LENGTH = getattr(settings, 'DPASTE_MAX_CONTENT_LENGTH', 250*1024*1024) 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): class SnippetForm(forms.ModelForm):
content = forms.CharField( content = forms.CharField(
label=_('Content'), label=_('Content'),
@ -29,7 +28,7 @@ class SnippetForm(forms.ModelForm):
lexer = forms.ChoiceField( lexer = forms.ChoiceField(
label=_(u'Lexer'), label=_(u'Lexer'),
initial=LEXER_DEFAULT, initial=LEXER_DEFAULT,
widget=forms.TextInput, choices=LEXER_LIST,
) )
expire_options = forms.ChoiceField( expire_options = forms.ChoiceField(
@ -55,35 +54,23 @@ class SnippetForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs): def __init__(self, request, *args, **kwargs):
super(SnippetForm, self).__init__(*args, **kwargs) super(SnippetForm, self).__init__(*args, **kwargs)
self.request = request 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 # Set the recently used lexer if we have any
session_lexer = self.request.session.get('lexer') session_lexer = self.request.session.get('lexer')
if session_lexer and session_lexer in dict(LEXER_LIST).keys(): if session_lexer and session_lexer in dict(LEXER_LIST).keys():
self.fields['lexer'].initial = session_lexer 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): def clean_content(self):
return self.cleaned_data.get('content', '').strip() return self.cleaned_data.get('content', '').strip()
def clean(self): def clean(self):
# The `title` field is a hidden honeypot field. If its filled,
# this is likely spam.
if self.cleaned_data.get('title'): if self.cleaned_data.get('title'):
raise forms.ValidationError('This snippet was identified as Spam.') raise forms.ValidationError('This snippet was identified as Spam.')
return self.cleaned_data return self.cleaned_data
def save(self, parent=None, *args, **kwargs): def save(self, parent=None, *args, **kwargs):
# Set parent snippet # Set parent snippet
if parent: if parent:
self.instance.parent = parent self.instance.parent = parent
@ -97,7 +84,7 @@ class SnippetForm(forms.ModelForm):
# Add the snippet to the user session list # Add the snippet to the user session list
if self.request.session.get('snippet_list', False): 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'].pop(0)
self.request.session['snippet_list'] += [self.instance.pk] self.request.session['snippet_list'] += [self.instance.pk]
else: else:
@ -106,4 +93,4 @@ class SnippetForm(forms.ModelForm):
# Save the lexer in the session so we can use it later again # Save the lexer in the session so we can use it later again
self.request.session['lexer'] = self.cleaned_data['lexer'] 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 import highlight
from pygments.lexers import * from pygments.lexers import *
from pygments.lexers import get_all_lexers
from pygments.formatters import HtmlFormatter 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 # The list of lexers. Its not worth to autogenerate this. See above how to
logger = logging.getLogger(__name__) # 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 # The default lexer is python
LEXER_LIST = sorted([(i[0], i[0]) for i in get_all_lexers() if not ( LEXER_DEFAULT = getattr(settings, 'DPASTE_LEXER_DEFAULT', 'python')
'+' in i[0] or
'with' in i[0].lower() or # Lexers which have wordwrap enabled by default
i[0].islower() LEXER_WORDWRAP = getattr(settings, 'DPASTE_LEXER_WORDWRAP', ('text', 'rst'))
)])
LEXER_LIST_NAME = dict([(i[0], i[1][0]) for i in get_all_lexers()])
LEXER_DEFAULT = 'Python'
LEXER_WORDWRAP = ('text', 'rst')
class NakedHtmlFormatter(HtmlFormatter): class NakedHtmlFormatter(HtmlFormatter):
def wrap(self, source, outfile): def wrap(self, source, outfile):
@ -28,25 +107,6 @@ class NakedHtmlFormatter(HtmlFormatter):
yield i, t yield i, t
def pygmentize(code_string, lexer_name=LEXER_DEFAULT): def pygmentize(code_string, lexer_name=LEXER_DEFAULT):
lexer_name = LEXER_LIST_NAME.get(lexer_name, None) lexer = lexer_name and get_lexer_by_name(lexer_name) \
try: or PythonLexer()
if lexer_name: return highlight(code_string, lexer, NakedHtmlFormatter())
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

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

View file

@ -19,4 +19,4 @@ DATABASES = {
SECRET_KEY = 'changeme' 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 %} {% block script_footer %}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"></script> <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 %} {% endblock %}
</body> </body>

View file

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

View file

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

View file

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

View file

@ -18,11 +18,6 @@ class SnippetAPITestCase(TestCase):
def test_empty(self): def test_empty(self):
""" """
The browser sent a content field but with no data. 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 = {} data = {}

View file

@ -2,11 +2,11 @@ from django.conf.urls.defaults import url, patterns
urlpatterns = patterns('dpaste.views', urlpatterns = patterns('dpaste.views',
url(r'^$', 'snippet_new', name='snippet_new'), 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'^diff/$', 'snippet_diff', name='snippet_diff'),
url(r'^history/$', 'snippet_history', name='snippet_history'), url(r'^history/$', 'snippet_history', name='snippet_history'),
url(r'^delete/$', 'snippet_delete', name='snippet_delete'), 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]+)/?$', '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]+)/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]+)/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'), 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 difflib
import requests 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.template.context import RequestContext
from django.http import (Http404, HttpResponseRedirect, HttpResponseBadRequest, from django.http import (Http404, HttpResponseRedirect, HttpResponseBadRequest,
HttpResponse, HttpResponseForbidden) HttpResponse, HttpResponseForbidden)
from django.conf import settings 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.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils import simplejson from django.utils import simplejson
from django.db.models import Count from django.db.models import Count
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,
server_error as django_server_error) server_error as django_server_error)
from dpaste.forms import SnippetForm from dpaste.forms import SnippetForm
from dpaste.models import Snippet from dpaste.models import Snippet
from dpaste.highlight import guess_code_lexer, \ from dpaste.highlight import LEXER_WORDWRAP, LEXER_LIST
LEXER_WORDWRAP, LEXER_LIST
# -----------------------------------------------------------------------------
# Snippet Handling
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)
)
def snippet_new(request, template_name='dpaste/snippet_new.html'): def snippet_new(request, template_name='dpaste/snippet_new.html'):
"""
Create a new snippet.
"""
if request.method == "POST": if request.method == "POST":
snippet_form = SnippetForm(data=request.POST, request=request) snippet_form = SnippetForm(data=request.POST, request=request)
if snippet_form.is_valid(): if snippet_form.is_valid():
request, new_snippet = snippet_form.save() new_snippet = snippet_form.save()
url = new_snippet.get_absolute_url() url = new_snippet.get_absolute_url()
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
@ -48,6 +39,7 @@ def snippet_new(request, template_name='dpaste/snippet_new.html'):
template_context = { template_context = {
'snippet_form': snippet_form, 'snippet_form': snippet_form,
'lexer_list': LEXER_LIST,
'is_new': True, '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): 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: try:
snippet = Snippet.objects.get(secret_id=snippet_id) snippet = Snippet.objects.get(secret_id=snippet_id)
except MultipleObjectsReturned: except MultipleObjectsReturned:
@ -98,7 +76,7 @@ def snippet_details(request, snippet_id, template_name='dpaste/snippet_details.h
if request.method == "POST": if request.method == "POST":
snippet_form = SnippetForm(data=request.POST, request=request, initial=new_snippet_initial) snippet_form = SnippetForm(data=request.POST, request=request, initial=new_snippet_initial)
if snippet_form.is_valid(): 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() url = new_snippet.get_absolute_url()
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
@ -125,34 +103,31 @@ def snippet_details(request, snippet_id, template_name='dpaste/snippet_details.h
else: else:
return response 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) 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() snippet.delete()
return HttpResponseRedirect(reverse('snippet_new') + '?delete=1') 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: try:
snippet_list = get_list_or_404(Snippet, pk__in=request.session.get('snippet_list', None)) snippet_list = get_list_or_404(Snippet, pk__in=request.session.get('snippet_list', None))
except ValueError: except ValueError:
snippet_list = None snippet_list = None
template_context = { 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, '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'): 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() \ if request.GET.get('a') and request.GET.get('a').isdigit() \
and request.GET.get('b') and request.GET.get('b').isdigit(): and request.GET.get('b') and request.GET.get('b').isdigit():
try: try:
@ -207,6 +184,7 @@ def snippet_diff(request, template_name='dpaste/snippet_diff.html'):
RequestContext(request) RequestContext(request)
) )
def snippet_gist(request, snippet_id): def snippet_gist(request, snippet_id):
""" """
Put a snippet on Github Gist. Put a snippet on Github Gist.
@ -235,15 +213,58 @@ def snippet_gist(request, snippet_id):
return HttpResponseRedirect(gist_url) return HttpResponseRedirect(gist_url)
# -----------------------------------------------------------------------------
# Static pages
# -----------------------------------------------------------------------------
def guess_lexer(request): def about(request, template_name='dpaste/about.html'):
code_string = request.GET.get('codestring', False) """
response = simplejson.dumps({'lexer': guess_code_lexer(code_string)}) 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) 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'): def page_not_found(request, template_name='dpaste/404.html'):
return django_page_not_found(request, template_name) return django_page_not_found(request, template_name)
def server_error(request, template_name='dpaste/500.html'): def server_error(request, template_name='dpaste/500.html'):
return django_server_error(request, template_name) return django_server_error(request, template_name)

View file

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