Implemented private pastes and improved some code. Added TODO file.

This commit is contained in:
mitsuhiko 2008-06-25 01:06:42 +02:00
parent dffba5bfbf
commit 790402b5df
11 changed files with 172 additions and 91 deletions

4
TODO Normal file
View File

@ -0,0 +1,4 @@
* Put all cookie stuff into *one* securecookie
* Make it possible to tag and find pastes
* Add a button to find all the personal (and private) pastes
(cookie bound)

View File

@ -17,7 +17,7 @@ from werkzeug import SharedDataMiddleware, ClosingIterator
from werkzeug.exceptions import HTTPException, NotFound from werkzeug.exceptions import HTTPException, NotFound
from lodgeit.utils import _local_manager, ctx, jinja_environment, \ from lodgeit.utils import _local_manager, ctx, jinja_environment, \
Request, generate_user_hash Request
from lodgeit.database import metadata, session, Paste from lodgeit.database import metadata, session, Paste
from lodgeit.urls import urlmap from lodgeit.urls import urlmap
from lodgeit.controllers import get_controller from lodgeit.controllers import get_controller

View File

@ -27,18 +27,18 @@ class PasteController(BaseController):
"""The 'create a new paste' view.""" """The 'create a new paste' view."""
code = error = '' code = error = ''
language = 'text' language = 'text'
pastes = session.query(Paste) show_captcha = private = False
show_captcha = False parent = None
getform = ctx.request.form.get getform = ctx.request.form.get
if ctx.request.method == 'POST': if ctx.request.method == 'POST':
code = getform('code') code = getform('code')
language = getform('language') language = getform('language')
try:
parent = pastes.filter(Paste.paste_id == parent_id = getform('parent')
int(getform('parent'))).first() if parent_id is not None:
except (ValueError, TypeError): parent = Paste.get(parent_id)
parent = None
spam = ctx.request.form.get('webpage') or antispam.is_spam(code) spam = ctx.request.form.get('webpage') or antispam.is_spam(code)
if spam: if spam:
error = 'your paste contains spam' error = 'your paste contains spam'
@ -51,16 +51,19 @@ class PasteController(BaseController):
show_captcha = True show_captcha = True
if code and language and not error: if code and language and not error:
paste = Paste(code, language, parent, ctx.request.user_hash) paste = Paste(code, language, parent, ctx.request.user_hash)
session.save(paste) if 'private' in ctx.request.form:
paste.private = True
session.flush() session.flush()
return redirect(paste.url) return redirect(paste.url)
else: else:
parent = ctx.request.args.get('reply_to') parent_id = ctx.request.args.get('reply_to')
if parent is not None and parent.isdigit(): if parent_id is not None:
parent = pastes.filter(Paste.paste_id == parent).first() parent = Paste.get(parent_id)
code = parent.code if parent is not None:
language = parent.language code = parent.code
language = parent.language
private = parent.private
return render_template('new_paste.html', return render_template('new_paste.html',
languages=LANGUAGES, languages=LANGUAGES,
@ -68,14 +71,14 @@ class PasteController(BaseController):
code=code, code=code,
language=language, language=language,
error=error, error=error,
show_captcha=show_captcha show_captcha=show_captcha,
private=private
) )
def show_paste(self, paste_id, raw=False): def show_paste(self, identifier, raw=False):
"""Show an existing paste.""" """Show an existing paste."""
linenos = ctx.request.args.get('linenos') != 'no' linenos = ctx.request.args.get('linenos') != 'no'
pastes = session.query(Paste) paste = Paste.get(identifier)
paste = pastes.filter(Paste.c.paste_id == paste_id).first()
if paste is None: if paste is None:
raise NotFound() raise NotFound()
if raw: if raw:
@ -90,18 +93,18 @@ class PasteController(BaseController):
linenos=linenos, linenos=linenos,
) )
def raw_paste(self, paste_id): def raw_paste(self, identifier):
"""Show an existing paste in raw mode.""" """Show an existing paste in raw mode."""
return self.show_paste(paste_id, raw=True) return self.show_paste(identifier, raw=True)
def show_tree(self, paste_id): def show_tree(self, identifier):
"""Display the tree of some related pastes.""" """Display the tree of some related pastes."""
paste = Paste.resolve_root(paste_id) paste = Paste.resolve_root(identifier)
if paste is None: if paste is None:
raise NotFound() raise NotFound()
return render_template('paste_tree.html', return render_template('paste_tree.html',
paste=paste, paste=paste,
current=paste_id current=identifier
) )
def show_all(self, page=1): def show_all(self, page=1):
@ -112,31 +115,30 @@ class PasteController(BaseController):
return '/all/' return '/all/'
return '/all/%d' % page return '/all/%d' % page
pastes = session.query(Paste).order_by( all = Paste.find_all()
Paste.c.pub_date.desc() pastes = all.limit(10).offset(10 * (page -1)).all()
).limit(10).offset(10*(page-1)).all()
if not pastes and page != 1: if not pastes and page != 1:
raise NotFound() raise NotFound()
return render_template('show_all.html', return render_template('show_all.html',
pastes=pastes, pastes=pastes,
pagination=generate_pagination(page, 10, pagination=generate_pagination(page, 10, all.count(), link),
Paste.count(), link),
css=get_style(ctx.request)[1] css=get_style(ctx.request)[1]
) )
def compare_paste(self, new_id=None, old_id=None): def compare_paste(self, new_id=None, old_id=None):
"""Render a diff view for two pastes.""" """Render a diff view for two pastes."""
# redirect for the compare form box # redirect for the compare form box
if old_id is new_id is None: if old_id is None:
old_id = ctx.request.form.get('old', '-1').lstrip('#') old_id = ctx.request.form.get('old', '-1').lstrip('#')
new_id = ctx.request.form.get('new', '-1').lstrip('#') new_id = ctx.request.form.get('new', '-1').lstrip('#')
return redirect('/compare/%s/%s' % (old_id, new_id)) return redirect('/compare/%s/%s' % (old_id, new_id))
pastes = session.query(Paste)
old = pastes.filter(Paste.c.paste_id == old_id).first() old = Paste.get(old_id)
new = pastes.filter(Paste.c.paste_id == new_id).first() new = Paste.get(new_id)
if old is None or new is None: if old is None or new is None:
raise NotFound() raise NotFound()
return render_template('compare_paste.html', return render_template('compare_paste.html',
old=old, old=old,
new=new, new=new,
@ -145,11 +147,12 @@ class PasteController(BaseController):
def unidiff_paste(self, new_id=None, old_id=None): def unidiff_paste(self, new_id=None, old_id=None):
"""Render an udiff for the two pastes.""" """Render an udiff for the two pastes."""
pastes = session.query(Paste) old = Paste.get(old_id)
old = pastes.filter(Paste.c.paste_id == old_id).first() new = Paste.get(new_id)
new = pastes.filter(Paste.c.paste_id == new_id).first()
if old is None or new is None: if old is None or new is None:
raise NotFound() raise NotFound()
return Response(old.compare_to(new), mimetype='text/plain') return Response(old.compare_to(new), mimetype='text/plain')
def set_colorscheme(self): def set_colorscheme(self):

View File

@ -34,10 +34,15 @@ def pastes_new_paste(language, code, parent_id=None,
""" """
if not language: if not language:
language = get_language_for(filename or '', mimetype or '') language = get_language_for(filename or '', mimetype or '')
paste = Paste(code, language, parent_id) parent = None
session.save(paste) if parent_id:
parent = Paste.get(parent_id)
if parent is None:
raise ValueError('parent paste not found')
paste = Paste(code, language, parent)
session.flush() session.flush()
return paste.paste_id return paste.identifier
@exported('pastes.getPaste') @exported('pastes.getPaste')
@ -48,8 +53,7 @@ def pastes_get_paste(paste_id):
`paste_id`, `code`, `parsed_code`, `pub_date`, `language`, `paste_id`, `code`, `parsed_code`, `pub_date`, `language`,
`parent_id`, `url`. `parent_id`, `url`.
""" """
paste = session.query(Paste).filter(Paste.c.paste_id == paste = Paste.get(paste_id)
paste_id).first()
if paste is None: if paste is None:
return False return False
return paste.to_xmlrpc_dict() return paste.to_xmlrpc_dict()
@ -58,11 +62,10 @@ def pastes_get_paste(paste_id):
@exported('pastes.getDiff') @exported('pastes.getDiff')
def pastes_get_diff(old_id, new_id): def pastes_get_diff(old_id, new_id):
"""Compare the two pastes and return an unified diff.""" """Compare the two pastes and return an unified diff."""
pastes = session.query(Paste) old = Paste.get(old_id)
old = pastes.filter(Paste.c.paste_id == old_id).first() new = Paste.get(new_id)
new = pastes.filter(Paste.c.paste_id == new_id).first()
if old is None or new is None: if old is None or new is None:
return False raise ValueError('argument error, paste not found')
return old.compare_to(new) return old.compare_to(new)
@ -72,10 +75,7 @@ def pastes_get_recent(amount=5):
`amount` pastes. `amount` pastes.
""" """
amount = min(amount, 20) amount = min(amount, 20)
return [x.to_xmlrpc_dict() for x in return [x.to_xmlrpc_dict() for x in Paste.find_all().limit(amount)]
session.query(Paste).order_by(
Paste.pub_date.desc()
).limit(amount)]
@exported('pastes.getLast') @exported('pastes.getLast')

View File

@ -19,7 +19,7 @@ from sqlalchemy.orm import create_session, mapper, backref, relation
from sqlalchemy.orm.scoping import ScopedSession from sqlalchemy.orm.scoping import ScopedSession
from werkzeug import cached_property from werkzeug import cached_property
from lodgeit.utils import _local_manager, ctx from lodgeit.utils import _local_manager, ctx, generate_paste_hash
from lodgeit.lib.highlighting import highlight, LANGUAGES from lodgeit.lib.highlighting import highlight, LANGUAGES
@ -29,9 +29,10 @@ metadata = MetaData()
pastes = Table('pastes', metadata, pastes = Table('pastes', metadata,
Column('paste_id', Integer, primary_key=True), Column('paste_id', Integer, primary_key=True),
Column('private_id', String(40), unique=True, nullable=True),
Column('code', Text), Column('code', Text),
Column('parent_id', Integer, ForeignKey('pastes.paste_id'), Column('parent_id', Integer, ForeignKey('pastes.paste_id'),
nullable=True), nullable=True),
Column('pub_date', DateTime), Column('pub_date', DateTime),
Column('language', String(30)), Column('language', String(30)),
Column('user_hash', String(40), nullable=True), Column('user_hash', String(40), nullable=True),
@ -40,6 +41,7 @@ pastes = Table('pastes', metadata,
class Paste(object): class Paste(object):
"""Represents a paste."""
def __init__(self, code, language, parent=None, user_hash=None): def __init__(self, code, language, parent=None, user_hash=None):
if language not in LANGUAGES: if language not in LANGUAGES:
@ -54,16 +56,83 @@ class Paste(object):
self.handled = False self.handled = False
self.user_hash = user_hash self.user_hash = user_hash
@staticmethod
def get(identifier):
"""Return the paste for an identifier. Private pastes must be loaded
with their unique hash and public with the paste id.
"""
if isinstance(identifier, basestring) and not identifier.isdigit():
return Paste.query.filter(Paste.private_id == identifier).first()
return Paste.query.filter(
(Paste.paste_id == int(identifier)) &
(Paste.private_id == None)
).first()
@staticmethod
def find_all():
"""Return a query for all public pastes ordered by the publication
date in reverse order.
"""
return Paste.query.filter(Paste.private_id == None) \
.order_by(Paste.pub_date.desc())
@staticmethod
def count():
"""COunt all pastes."""
s = select([func.count(pastes.c.paste_id)])
return session.execute(s).fetchone()[0]
@staticmethod
def resolve_root(identifier):
"""Find the root paste for a paste tree."""
paste = Paste.get(identifier)
if paste is None:
return
while paste.parent_id is not None:
paste = paste.parent
return paste
def _get_private(self):
return self.private_id is not None
def _set_private(self, value):
if not value:
self.private_id = None
return
if self.private_id is None:
while 1:
self.private_id = generate_paste_hash()
paste = Paste.query.filter(Paste.private_id ==
self.private_id).first()
if paste is None:
break
private = property(_get_private, _set_private, doc='''
The private status of the paste. If the paste is private it gets
a unique hash as identifier, otherwise an integer.
''')
del _get_private, _set_private
@property
def identifier(self):
"""The paste identifier. This is a string, the same the `get`
method accepts.
"""
if self.private:
return self.private_id
return str(self.paste_id)
@property @property
def url(self): def url(self):
return '/show/%d/' % self.paste_id """The URL to the paste."""
return '/show/%s/' % self.identifier
def compare_to(self, other, context_lines=4, template=False): def compare_to(self, other, context_lines=4, template=False):
"""Compare the paste with another paste."""
udiff = u'\n'.join(difflib.unified_diff( udiff = u'\n'.join(difflib.unified_diff(
self.code.splitlines(), self.code.splitlines(),
other.code.splitlines(), other.code.splitlines(),
fromfile='Paste #%d' % self.paste_id, fromfile='Paste #%s' % self.identifier,
tofile='Paste #%d' % other.paste_id, tofile='Paste #%s' % other.identifier,
lineterm='', lineterm='',
n=context_lines n=context_lines
)) ))
@ -75,9 +144,11 @@ class Paste(object):
@cached_property @cached_property
def parsed_code(self): def parsed_code(self):
"""The paste as rendered code."""
return highlight(self.code, self.language) return highlight(self.code, self.language)
def to_xmlrpc_dict(self): def to_xmlrpc_dict(self):
"""Convert the paste into a dict for XMLRCP."""
from lodgeit.lib.xmlrpc import strip_control_chars from lodgeit.lib.xmlrpc import strip_control_chars
return { return {
'paste_id': self.paste_id, 'paste_id': self.paste_id,
@ -90,6 +161,7 @@ class Paste(object):
} }
def render_preview(self): def render_preview(self):
"""Render a preview for this paste."""
try: try:
start = self.parsed_code.index('</pre>') start = self.parsed_code.index('</pre>')
code = self.parsed_code[ code = self.parsed_code[
@ -110,7 +182,7 @@ class Paste(object):
Paste.user_hash == ctx.request.user_hash Paste.user_hash == ctx.request.user_hash
) )
paste_list = session.query(Paste).filter(and_( paste_list = Paste.query.filter(and_(
Paste.parent_id.in_(s), Paste.parent_id.in_(s),
Paste.handled == False, Paste.handled == False,
Paste.user_hash != ctx.request.user_hash, Paste.user_hash != ctx.request.user_hash,
@ -121,24 +193,8 @@ class Paste(object):
values={'handled': True})) values={'handled': True}))
return paste_list return paste_list
@staticmethod
def count():
s = select([func.count(pastes.c.paste_id)])
return session.execute(s).fetchone()[0]
@staticmethod session.mapper(Paste, pastes, properties={
def resolve_root(paste_id):
pastes = session.query(Paste)
while True:
paste = pastes.filter(Paste.c.paste_id == paste_id).first()
if paste is None:
return
if paste.parent_id is None:
return paste
paste_id = paste.parent_id
mapper(Paste, pastes, properties={
'children': relation(Paste, 'children': relation(Paste,
primaryjoin=pastes.c.parent_id==pastes.c.paste_id, primaryjoin=pastes.c.parent_id==pastes.c.paste_id,
cascade='all', cascade='all',

View File

@ -5,7 +5,7 @@
The URL mapping. The URL mapping.
:copyright: 2007 by Armin Ronacher. :copyright: 2007-2008 by Armin Ronacher.
:license: BSD :license: BSD
""" """
from werkzeug.routing import Map, Rule from werkzeug.routing import Map, Rule
@ -13,12 +13,12 @@ from werkzeug.routing import Map, Rule
urlmap = Map([ urlmap = Map([
# paste interface # paste interface
Rule('/', endpoint='pastes/new_paste'), Rule('/', endpoint='pastes/new_paste'),
Rule('/show/<int:paste_id>/', endpoint='pastes/show_paste'), Rule('/show/<identifier>/', endpoint='pastes/show_paste'),
Rule('/raw/<int:paste_id>/', endpoint='pastes/raw_paste'), Rule('/raw/<identifier>/', endpoint='pastes/raw_paste'),
Rule('/compare/', endpoint='pastes/compare_paste'), Rule('/compare/', endpoint='pastes/compare_paste'),
Rule('/compare/<int:new_id>/<int:old_id>/', endpoint='pastes/compare_paste'), Rule('/compare/<new_id>/<old_id>/', endpoint='pastes/compare_paste'),
Rule('/unidiff/<int:new_id>/<int:old_id>/', endpoint='pastes/unidiff_paste'), Rule('/unidiff/<new_id>/<old_id>/', endpoint='pastes/unidiff_paste'),
Rule('/tree/<int:paste_id>/', endpoint='pastes/show_tree'), Rule('/tree/<identifier>/', endpoint='pastes/show_tree'),
# captcha for new paste # captcha for new paste
Rule('/_captcha.png', endpoint='pastes/show_captcha'), Rule('/_captcha.png', endpoint='pastes/show_captcha'),

View File

@ -8,6 +8,7 @@
:copyright: 2007 by Christopher Grebs. :copyright: 2007 by Christopher Grebs.
:license: BSD :license: BSD
""" """
import re
import time import time
from os import path from os import path
try: try:
@ -15,6 +16,7 @@ try:
except: except:
from sha import new as sha1 from sha import new as sha1
from random import random from random import random
from functools import partial
from werkzeug import Local, LocalManager, LocalProxy, \ from werkzeug import Local, LocalManager, LocalProxy, \
Request as RequestBase, Response Request as RequestBase, Response
@ -30,9 +32,12 @@ jinja_environment = Environment(loader=FileSystemLoader(
path.join(path.dirname(__file__), 'views'))) path.join(path.dirname(__file__), 'views')))
_word_only = partial(re.compile(r'[^a-zA-Z0-9]').sub, '')
def datetimeformat(obj): def datetimeformat(obj):
"""Helper filter for the template""" """Helper filter for the template"""
return obj.strftime('%Y-%m-%d %H:%M') return obj.strftime('%Y-%m-%d @ %H:%M')
jinja_environment.filters['datetimeformat'] = datetimeformat jinja_environment.filters['datetimeformat'] = datetimeformat
@ -42,6 +47,16 @@ def generate_user_hash():
return sha1('%s|%s' % (random(), time.time())).hexdigest() return sha1('%s|%s' % (random(), time.time())).hexdigest()
def generate_paste_hash():
"""Generates a more or less unique-truncated SHA1 hash."""
while 1:
digest = sha1('%s|%s' % (random(), time.time())).digest()
val = _word_only(digest.encode('base64').strip().splitlines()[0])[:20]
# sanity check. number only not allowed (though unlikely)
if not val.isdigit():
return val
class Request(RequestBase): class Request(RequestBase):
"""Subclass of the `Request` object. automatically creates a new """Subclass of the `Request` object. automatically creates a new
`user_hash` and sets `first_visit` to `True` if it's a new user. `user_hash` and sets `first_visit` to `True` if it's a new user.

View File

@ -4,8 +4,8 @@
{% block body %} {% block body %}
<p> <p>
Differences between the pastes Differences between the pastes
<a href="{{ old.url|e }}">#{{ old.paste_id }}</a> ({{ old.pub_date|datetimeformat }}) <a href="{{ old.url|e }}">#{{ old.identifier }}</a> ({{ old.pub_date|datetimeformat }})
and <a href="{{ new.url|e }}">#{{ new.paste_id }}</a> ({{ new.pub_date|datetimeformat }}). Download as <a href="/unidiff/{{ old.paste_id }}/{{ new.paste_id }}/">unified diff</a>. and <a href="{{ new.url|e }}">#{{ new.identifier }}</a> ({{ new.pub_date|datetimeformat }}). Download as <a href="/unidiff/{{ old.identifier }}/{{ new.identifier }}/">unified diff</a>.
</p> </p>
{% if diff.chunks %} {% if diff.chunks %}
<table class="diff"> <table class="diff">

View File

@ -10,7 +10,8 @@
{%- if show_captcha %} {%- if show_captcha %}
<div class="captcha"> <div class="captcha">
<p>Please fill out the CAPTCHA to proceed:</p> <p>Please fill out the CAPTCHA to proceed:</p>
<img src="/_captcha.png" alt="a captcha you can't see. Sorry :("> <img src="{{ url_for('pastes/captcha')
}}" alt="a captcha you can't see. Sorry :(">
<input type="text" name="captcha" size="20"> <input type="text" name="captcha" size="20">
</div> </div>
{%- else %} {%- else %}
@ -19,7 +20,7 @@
</div> </div>
{%- endif %} {%- endif %}
{%- if parent %} {%- if parent %}
<input type="hidden" name="parent" value="{{ parent.paste_id }}"> <input type="hidden" name="parent" value="{{ parent.identifier }}">
{%- endif %} {%- endif %}
<textarea name="code" rows="10" cols="80">{{ code|e }}</textarea> <textarea name="code" rows="10" cols="80">{{ code|e }}</textarea>
<select name="language"> <select name="language">
@ -32,5 +33,7 @@
<input type="submit" value="Paste!"> <input type="submit" value="Paste!">
<input type="button" value="▲" onclick="LodgeIt.resizeTextarea(-100)"> <input type="button" value="▲" onclick="LodgeIt.resizeTextarea(-100)">
<input type="button" value="▼" onclick="LodgeIt.resizeTextarea(100)"> <input type="button" value="▼" onclick="LodgeIt.resizeTextarea(100)">
<input type="checkbox" name="private" id="private"{% if private
%} checked{% endif %}><label for="private">make this paste private</label>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -5,7 +5,7 @@
<ul class="paste_list"> <ul class="paste_list">
{% for paste in pastes %} {% for paste in pastes %}
<li class="{{ loop.cycle('even', 'odd') }}"><p><a href="{{ paste.url|e <li class="{{ loop.cycle('even', 'odd') }}"><p><a href="{{ paste.url|e
}}">Paste #{{ paste.paste_id }}</a>, }}">Paste #{{ paste.identifier }}</a>,
pasted on {{ paste.pub_date|datetimeformat }}</p> pasted on {{ paste.pub_date|datetimeformat }}</p>
{{ paste.render_preview() }}</li> {{ paste.render_preview() }}</li>
{%- endfor %} {%- endfor %}

View File

@ -1,5 +1,5 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% set page_title = 'Paste #%d'|format(paste.paste_id) %} {% set page_title = 'Paste #' ~ paste.identifier %}
{% set active_page = 'all' %} {% set active_page = 'all' %}
{% block body %} {% block body %}
<div class="related"> <div class="related">
@ -7,26 +7,26 @@
<div class="content"> <div class="content">
<p>posted on {{ paste.pub_date|datetimeformat }}</p> <p>posted on {{ paste.pub_date|datetimeformat }}</p>
<ul> <ul>
<li><a class="autoclose" href="/?reply_to={{ paste.paste_id }}">reply to this paste</a></li> <li><a class="autoclose" href="/?reply_to={{ paste.identifier }}">reply to this paste</a></li>
{% if paste.parent %} {% if paste.parent %}
<li><a class="autoclose" href="/compare/{{ paste.paste_id }}/{{ <li><a class="autoclose" href="/compare/{{ paste.identifier }}/{{
paste.parent.paste_id }}/">compare it with the parent paste</a></li> paste.parent.identifier }}/">compare it with the parent paste</a></li>
<li><a class="autoclose" href="{{ paste.parent.url|e }}">look at the parent paste</a></li> <li><a class="autoclose" href="{{ paste.parent.url|e }}">look at the parent paste</a></li>
{% endif %} {% endif %}
{% if paste.children %} {% if paste.children %}
<li>the following pastes replied to this paste: <li>the following pastes replied to this paste:
{% for child in paste.children %} {% for child in paste.children %}
<a class="autoclose" href="{{ child.url|e }}">#{{ child.paste_id }}</a> <a class="autoclose" href="{{ child.url|e }}">#{{ child.identifier }}</a>
{%- if not loop.last %},{% endif -%} {%- if not loop.last %},{% endif -%}
{% endfor %} {% endfor %}
</li> </li>
{% endif %} {% endif %}
{% if paste.parent or paste.children %} {% if paste.parent or paste.children %}
<li><a href="/tree/{{ paste.paste_id }}/">show paste tree</a></li> <li><a href="/tree/{{ paste.identifier }}/">show paste tree</a></li>
{% endif %} {% endif %}
<li><a href="/raw/{{ paste.paste_id }}/">download paste</a></li> <li><a href="/raw/{{ paste.identifier }}/">download paste</a></li>
<li>compare with paste <form action="/compare/" method="post"> <li>compare with paste <form action="/compare/" method="post">
<input type="hidden" name="old" value="{{ paste.paste_id }}"> <input type="hidden" name="old" value="{{ paste.identifier }}">
<input type="text" name="new" value="#"> <input type="text" name="new" value="#">
<input type="submit" value="compare"> <input type="submit" value="compare">
</form></li> </form></li>