Commits

Anonymous committed bad1e02

major change in the way we manage paste id. We now use the document id provided by couchdb and encode it to base62. While I was here, fix a lot of stuff (lock, delete, revisions...)

Comments (0)

Files changed (23)

friendpaste/_design/paste/by_id/map.js

 function (doc) { 
     if (doc.itemType == "paste") {
-        emit(doc.pasteid, doc);  
+        emit(doc._id, doc);  
     }  
-}
+}

friendpaste/_design/paste/by_nbrevisions/map.js

 function (doc) {
     if (doc.itemType == "paste")
-        emit([doc.pasteid, doc.nb_revision], doc);
+        emit([doc._id, doc.revid], doc);
     else if (doc.itemType == 'revision')
-        emit([doc.parent, doc.nb_revision], doc);
+        emit([doc.parent, doc.revid], doc);
 
 }

friendpaste/_design/paste/with_revisions/map.js

 function (doc) {
     if (doc.itemType == "paste") {
-        emit([doc.pasteid, 0], doc);
+        emit([doc._id, 0], doc);
     } else if (doc.itemType == "revision") {
-        emit([doc.pasteid, 1], doc);
+        emit([doc.parent, 1], doc);
     }
-}
+}

friendpaste/_design/reviews/line_for_revision/map.js

 function(doc) {
     if (doc.itemType == 'review')
-        emit([doc.pasteid, doc.nb_line, doc.nb_revision], doc);
+        emit([doc.pasteid, doc.nb_line, doc.revision], doc);
 }

friendpaste/_design/reviews/lines_for_revision/map.js

 function(doc) {
     if (doc.itemType == 'review')
-        emit([doc.pasteid, doc.nb_revision], doc);
+        emit([doc.pasteid, doc.revision], doc);
 }

friendpaste/_design/reviews/reviews_count/map.js

 function(doc) {
     if (doc.itemType == 'review')
-        emit([doc.pasteid, doc.nb_revision], [doc.nb_line, doc.reviews.length]);
+        emit([doc.pasteid, doc.revision], [doc.nb_line, doc.reviews.length]);
 }

friendpaste/_design/reviews/revisions_for_line/map.js

 function(doc) {
     if (doc.itemType == 'review')
-        emit([doc.pasteid, doc.nb_line], doc.nb_revision);
+        emit([doc.pasteid, doc.nb_line], doc.revision);
 }

friendpaste/application.py

 # limitations under the License.
 
 import time
+from uuid import UUID
+
+
 from couchdb import Server, ServerError
 from werkzeug import Request, SharedDataMiddleware, ClosingIterator, redirect
 from werkzeug.exceptions import HTTPException, NotFound
 from friendpaste.utils import local, local_manager
 from friendpaste.http import FPRequest, session_store
 from friendpaste.models import Privacy
+from friendpaste.utils.base62 import b62decode
 
 class FriendpasteApp(object):
     def __init__(self):
         # get paste privacy here
         cur_path = [p for p in request.path.split('/') if p]
         if cur_path and cur_path not in OTHER_PATHS:
+            pasteid = UUID(int=b62decode(cur_path[0])).hex
             request.pasteid = cur_path[0]
-            request.privacy = Privacy.for_paste(local.db, request.pasteid)
+            request.privacy = Privacy.for_paste(local.db, pasteid)
         else:
             request.pasteid = None
             request.privacy = None

friendpaste/models.py

 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import base64
 from datetime import datetime
+from uuid import UUID
 
-from couchdb.client import ResourceNotFound
+from couchdb.client import ResourceNotFound, ResourceConflict
 from couchdb.schema import *
 
 from friendpaste.utils import local, make_hash, short
 from friendpaste.utils.diff import unified_diff, diff_blocks
+from friendpaste.utils.base62 import b62encode
 
 __all__ = ['Paste']
 
 class ArrayField(Field):
     _to_python = list
 
-def _genid():
-    charset = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
-    from random import choice
-    return ''.join([choice(charset) for i in range(8)])
-  
+
 class Review(Document):
     pasteid = TextField()
-    nb_revision = IntegerField()
+    revision = TextField()
     nb_line = IntegerField()
     reviews = ArrayField(default=[])
 
 
     @classmethod
     def get_line(cls, db, snippet, nb_line):
-        """Here we try to get current line reviews for this
-        snippet revision and previous and next revisions on which
-        we have reviews for this line. If no review is found
-        for this revision use the previous one, or if no previous, 
-        the next one"""
+        if snippet.itemType == "revision":
+            pasteid = snippet.parent
+        else:
+            pasteid = snippet.id
+
+        rows = db.view("_view/reviews/line_for_revision", 
+                key=[pasteid, int(nb_line), snippet.revid])
+        reviews = list(rows)
+        results = []
+        if reviews:
+            results = reviews[0].value['reviews']
+            results.sort(lambda a,b: cmp(a['created'], b['created']))
+            results.reverse()
         
-        lrevisions = []
-        result = {
-                'review': None,
-                'reviews': [],
-                'prev': -1,
-                'next': -1
-        }
-
-        # get all revisions for this line
-        try:
-            irevisions = db.view("_view/reviews/revisions_for_line",
-                key=[str(snippet.pasteid), int(nb_line)])
-            lrevisions = list(irevisions)
-        except:
-            pass
-
-        if lrevisions:
-            if len(lrevisions) == 1:
-                if lrevisions[0].value == snippet.nb_revision:
-                    ireviews = db.view("_view/reviews/line_for_revision", 
-                        key=[snippet.pasteid, int(nb_line), lrevisions[0].value])
-                    result['review']= list(ireviews)[0].value
-                    if result['review']['reviews']:
-                        result['reviews'] = [rev for rev in result['review']['reviews']]
-                else:
-                    if lrevisions[0].value < snippet.nb_revision:
-                        result['prev'] = lrevisions[0].value
-                    else:
-                        result['next'] = lrevisions[0].value
-            else:
-                revisions = [rev.value for rev in lrevisions]
-                revisions.sort()
-                if snippet.nb_revision in revisions:
-                    current = revisions.index(snippet.nb_revision)
-                    if current < len(revisions) - 1:
-                        result['next'] = revisions[current+1]
-                        if current > 0:
-                            result['prev'] = revisions[current-1]
-                    else:
-                        result['prev'] = revisions[current-1]
-
-                    ireviews = db.view("_view/reviews/line_for_revision", 
-                                key=[snippet.pasteid, int(nb_line), snippet.nb_revision])
-                    result['review'] = list(ireviews)[0].value
-                    if result['review']['reviews']:
-                        result['reviews'] = [rev for rev in result['review']['reviews']]
-                else:
-                    max_rev = max(revisions)
-                    if max_rev < snippet.nb_revision:
-                        result['prev'] = max_rev
-                    else:
-                        if len(revisions) == 2:
-                            result['next'] = revisions[1]
-                            result['prev'] = revisions[0]
-                        else:
-                            if (max_rev - snippet.nb_revision) < (snippet.nb_revision - revisions[0]):
-                                pivot = len(revisions) - 1
-                                while revisions[pivot] > snippet.nb_revision:
-                                    pivot = pivot - 1
-                            else:
-                                pivot = 0
-                                while revisions[pivot] < snipet.nb_revision:
-                                    pivot = pivot + 1
-                            result['prev'] = revisions[pivot]
-                            result['next'] = revisions[pivot+1]
-        return result
+        return results
 
     @classmethod
     def review_for_line(cls, db, snippet, nb_line):
+        if snippet.itemType == "revision":
+            pasteid = snippet.parent
+        else:
+            pasteid = snippet.id
+
         ireviews = cls.view(db, "_view/reviews/line_for_revision", 
-                key=[snippet.pasteid, int(nb_line), snippet.nb_revision])
+                key=[pasteid, int(nb_line), snippet.revid])
         results = list(ireviews)
         if results:
             return results[0]
         return None
 
     @classmethod
-    def get_lines_reviews(cls, db, pasteid, nb_revision):
-        results = cls.view(db, '_view/reviews/lines_for_revision', key=[pasteid, nb_revision])
+    def get_lines_reviews(cls, db, pasteid, revision):
+        results = cls.view(db, '_view/reviews/lines_for_revision', key=[pasteid, revision])
         return list(results)
 
     @classmethod
-    def get_reviews_counts(cls, db, pasteid, nb_revision):
-        results = db.view('_view/reviews/reviews_count', key=[pasteid, nb_revision])
+    def get_reviews_counts(cls, db, pasteid, revision):
+        results = db.view('_view/reviews/reviews_count', key=[pasteid, revision])
         return dict([row.value for row in list(results)])
 
     @classmethod
-    def update_positions(cls, db, pasteid, nb_revision, old_revision, unchanged):
+    def update_positions(cls, db, pasteid, revision, old_revision, unchanged):
         lines_reviews = cls.get_lines_reviews(db, pasteid, old_revision)
         docs = []
         for review in lines_reviews:
             if review.nb_line in unchanged:
                 docs.append({
                     'pasteid': pasteid,
-                    'nb_revision': nb_revision,
+                    'revision': revision,
                     'nb_line': unchanged[review.nb_line],
                     'reviews': review.reviews,
                     'itemType': 'review'
     
 class Fork(Document):
     fork_parent = TextField(default='')
-    forked_atrevison = IntegerField(default=0)
+    forked_atrevison = TextField(default='')
     fork_id = TextField()
     fork_date = DateTimeField(default=datetime.utcnow())
 
         return None 
 
 class Paste(Document):
-    pasteid = TextField(default='')
     title = TextField()
     parent = TextField(default='')
     previous  = TextField(default='')
     content = TextField(default='')
     language = TextField(default='text')
     changes = ArrayField(default=[])
-    nb_revision = IntegerField(default=0)
     fork = BooleanField(default=False)
     forked = BooleanField(default=False)
     fork_parent = TextField(default='')
             self.created = datetime.utcnow()
             node = make_hash(self.content, self.title, self.language,'')
             self.revid = short(node)
-            if not self.pasteid:
-                while 1:
-                    pasteid = _genid()
-                    if not self.is_exist(db, pasteid):
-                        self.pasteid = pasteid
-                        break
-                                       
             docid = db.create(self._data)
             self._data = db.get(docid)
         else:
-            old_data = db.get(self.id)
+            original_data =  self._data
+
+            if self.itemType == "revision":
+                old_data = db.get(self.parent)
+                self._data['_id'] = old_data['_id']
+                self._data['_rev'] = old_data['_rev']
+                self.itemType = 'paste'
+            else:
+                old_data = db.get(self.id)
+
             self.created = datetime.utcnow()
             old_hash = make_hash(old_data['content'],old_data['title'],old_data['language'], old_data['forked'])
             new_hash = make_hash(self.content,self.title,self.language, self.forked)
             if old_hash != new_hash:
                 del old_data['_id']
                 del old_data['_rev']
-                old_data['parent'] = self.pasteid
+                old_data['parent'] = self.id
                 old_data['itemType'] = 'revision'  
                 _previous = db.create(old_data)
                 
                 node = make_hash(self.content,self.title,self.language, old_data['revid'])
                 self.revid = short(node)
                 
-                # increment revision number
-                self.nb_revision = int(old_data['nb_revision']) + 1
-                
                 # save previous revision id, could be usefull
                 self.previous = _previous
                 
                 self.changes = _changes
             
                 # save changes
-                db[self.id] = self._data
+                try:
+                    db[self.id] = self._data
+                except ResourceConflict:
+                    # if conflict, remove our changes first
+                    # then raise.
+                    self._data = original_data
+                    if _previous:
+                        del db[_previous]
+
+                    raise ResourceConflict
+
             elif old_data['edit_code'] != self.edit_code or \
                     old_data['locked'] != self.locked:
                 db[self.id] = self._data
         return self
        
     @classmethod
-    def fork(cls, db, pasteid, new_code=None):
-        """ simple fork of a paste """
-        if new_code is None:
-            new_code = ''
+    def get_paste(cls, db, pasteid, revid):
+        if revid is not None:
+            return cls.for_revision(db, pasteid, revid)
 
-        paste = cls.get_paste(db, pasteid)
-        if paste is not None: # create new paste from old
-            new_paste = cls(
-                title = paste.title,
-                content = paste.content,
-                language = paste.language,
-                fork = True,
-                fork_parent = paste.pasteid,
-                forked_atrevision = paste.nb_revision,
-                edit_code = new_code
-            )
-            new_paste.store(local.db)
-           
-            paste.forked = True
-            paste.store(local.db)
-            
-            # save fork infos
-            new_fork = Fork(
-                    fork_parent = paste.pasteid,
-                    forked_atrevision = paste.nb_revision,
-                    fork_id = new_paste.pasteid
-            )
-            new_fork.store(local.db)
-            return new_paste
-        return paste
-
-    @classmethod
-    def get_paste(cls, db, pasteid):
-        rows = cls.view(db, '_view/paste/by_id', key=pasteid)
-        lrows = list(iter(rows))
-        if lrows:
-            return lrows[0]
-        return None
+        return cls.load(db, pasteid)
     
     @classmethod
-    def is_exist(cls, db, pasteid):
-        paste = cls.get_paste(db, pasteid)
-        if paste is not None:
+    def is_exist(cls, db, pasteid):   
+        if pasteid in db:
             return True
         return False
     
         if not self.id:
             return None
         rows = self.view(db, '_view/paste/by_nbrevisions',
-                key=[self.pasteid, int(revid)])
+                key=[self.pasteid, revid])
         rows = list(iter(rows))
         if rows:
             return rows[0]
 
-        rows = self.view(db, '_view/paste/by_revid', key=revid)
-        rows = list(iter(rows))
-        if rows:
-            return rows[0]
         return None
   
     @classmethod
-    def for_revision(cls, db, pasteid, nb_revision):
+    def for_revision(cls, db, pasteid, revid):
         rows = cls.view(db, '_view/paste/by_nbrevisions',
-                key=[pasteid, int(nb_revision)])
+                key=[pasteid, revid])
         rows = list(rows)
         if rows:
             return rows[0]
 
     def revisions(self, db):
         if not self.id or not self.previous:
-            return []  
+            return []
         rows = self.view(db, '_view/paste/revisions', key=self.id)
         revisions = list(iter(rows))
         if revisions:
         
     def get_changeset(self, db):
         previous = Paste.load(db, self.previous)
-        unidiff = '--- Revision %s\n+++ Revision %s\n' % (previous.nb_revision, self.nb_revision) + \
+        unidiff = '--- Revision %s\n+++ Revision %s\n' % (previous.revid, self.revid) + \
                 '\n'.join(unified_diff (previous.content.splitlines(), self.content.splitlines(), 3))
         tabular = diff_blocks(previous.content.splitlines(), self.content.splitlines(), 3, 8, 1, 0, 1)
         return unidiff, tabular, previous
     def with_revisions(cls, db, pasteid, revid):
         rows = cls.view(db, '_view/paste/with_revisions', startkey=[pasteid,0], endkey=[pasteid, 1])
         results = list(rows)
-        print results
+
+        encoded_id = b62encode(UUID(pasteid).int)
+        snippet = None
         if results:
             results.sort(lambda a,b: cmp(a.updated, b.updated))
             results.reverse()
-            i = 0
-            if revid:
-                for paste in results:
-                    if paste.nb_revision == revid or paste.revid == revid:
-                        break
-                    i = i + 1
-            try:            
-                return results[i], results[i+1:]
-            except IndexError:
-                try:
-                    return results[i], []
-                except IndexError:
-                    try:
-                        return results[i-1], []
-                    except IndexError:
-                        pass
+            revisions = []
+            for paste in results:
+                paste.pasteid = encoded_id
+                if revid is not None and paste.revid == revid:
+                    snippet = paste
+                else:
+                    revisions.append(paste)
+            return snippet, revisions
         return None, []
 
+    @classmethod
+    def lock(cls, db, pasteid, locked):
+        """ lock paste and all revisions """
+        rows = cls.view(db, '_view/paste/with_revisions', startkey=[pasteid,0], endkey=[pasteid, 1])
+        results = list(rows)
+
+        docs = []
+        if results:
+            for paste in results:
+                paste.locked = locked
+                docs.append(paste._data)
+
+            db.resource.post('_bulk_docs', { "docs": docs })
+
+
+    @classmethod
+    def delete(cls, db, pasteid):
+        """ delete paste with all its revisions """
+        rows = cls.view(db, '_view/paste/with_revisions', startkey=[pasteid,0], endkey=[pasteid, 1])
+        results = list(rows)
+        docs = []
+        if results:
+            for paste in results:
+                data = paste._data
+                data['_deleted'] = True
+                docs.append(data)
+            db.resource.post('_bulk_docs', { "docs": docs })
+
+

friendpaste/settings.py

 
 if DEBUG:
     SITE_URI = 'http://localhost:5000'
-    ORBITED_HOST = 'localhost'
+    ORBITED_HOST = '192.168.1.2'
     ORBITED_PORT = 9000
  
 

friendpaste/urls.py

 ])
 
 
-OTHER_PATHS = ['highlight', 'about', 'services', 'settings', '_all_languages']
+OTHER_PATHS = ['highlight', 'about', 'services', 'settings', '_all_languages', '.']

friendpaste/utils/__init__.py

     except UnicodeDecodeError, e:
         raise CouchitUnicodeDecodeError(s, *e.args)
     return s
-    
+  
+def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
+    """
+    Returns a bytestring version of 's', encoded as specified in 'encoding'.
+
+    If strings_only is True, don't convert (some) non-string-like objects.
+    """
+    if strings_only and isinstance(s, (types.NoneType, int)):
+        return s
+   
+    if not isinstance(s, basestring):
+        try:
+            return str(s)
+        except UnicodeEncodeError:
+            if isinstance(s, Exception):
+                # An Exception subclass containing non-ASCII data that doesn't
+                # know how to print itself properly. We shouldn't raise a
+                # further exception.
+                return ' '.join([smart_str(arg, encoding, strings_only,
+                        errors) for arg in s])
+            return unicode(s).encode(encoding, errors)
+    elif isinstance(s, unicode):
+        return s.encode(encoding, errors)
+    elif s and encoding != 'utf-8':
+        return s.decode('utf-8', errors).encode(encoding, errors)
+    else:
+        return s
+
 def make_hash(*args):
     if len(args) <= 0:
         return None
         s.update(to_str(arg))
 
     return _hex(s.digest())
-    
+   
 def short(node):
     return _hex(node[:6])
 

friendpaste/views.py

 import os.path
 import socket
 import time
+from uuid import UUID
 
-
+from couchdb.client import ResourceNotFound, ResourceConflict 
 from jinja2.filters import do_truncate, do_striptags, escape
 from wtforms import Form, TextField, TextAreaField, SelectField, PasswordField, \
 ValidationError, validators
 strptime, make_hash, datetime_tojson, send_stomp_msg, force_unicode
 from friendpaste.utils.diff import get_unchanged_lines
 from friendpaste.utils.html import sanitize_html
+from friendpaste.utils.base62 import *
+
 
 def _get_lexers():
     lexers = get_all_lexers()
         if field.data != "open" and not form.data['password']:
             raise ValidationError(u'Password is empty.')
 
+
 def login_required(f, privacy=['private']):
     def _decorated(request, **kwargs):
         authenticated = request.session.get(
 
 def create_snippet(request):
     mimetypes = request.accept_mimetypes
+    error = False
     if request.method=='POST' and 'application/json' in mimetypes:
         d=json.loads(request.data)
         lang=request.values.get("lang", None)
             s.store(local.db)
 
             p = Privacy(
-                pasteid = s.pasteid,
+                pasteid = s.id,
                 privacy = form.data['privacy'],
                 password = pwd
             )
             p.store(local.db)
         except:
             return send_json({'ok': False, 'reason': 'something wrong happend while saving data'})
-
+        encoded_id =  b62encode(UUID(s.id).int)
         return send_json({
             'ok': True,
-            'id': s.pasteid,
-            'nb_revision': s.nb_revision,
-            'url': '%s/%s' % (settings.SITE_URI, s.pasteid)
+            'id': encoded_id,
+            'rev': s.revid,
+            'url': '%s/%s' % (settings.SITE_URI, encoded_id)
         })
 
     form = PasteForm(request.form, prefix='paste')
         if pwd:
             pwd = make_hash(pwd)
 
-        s = Paste(
+        try:
+            s = Paste(
                 title=form.data['title'],
                 content=form.data['snippet'],
                 language=form.data['language'],
                 edit_code= code        
-        )
-        s.store(local.db)
+            )
+            s.store(local.db)
 
-        p = Privacy(
-            pasteid = s.pasteid,
-            privacy = form.data['privacy'],
-            password = pwd
-        )
-        p.store(local.db)
-        request.session['%s_authenticated' % s.pasteid] = True
+            p = Privacy(
+                pasteid = s.id,
+                privacy = form.data['privacy'],
+                password = pwd
+            )
+            p.store(local.db)
+
+            encoded_id =  b62encode(UUID(s.id).int)
+            request.session['%s_authenticated' % encoded_id] = True
         
-        return redirect ('/%s' % s.pasteid)
+            return redirect ('/%s' % encoded_id)
+        except:
+            error = True
 
-    return render_response('paste/index.html', form=form)
+    return render_response('paste/index.html', form=form, error=error)
 
 
 def edit_snippet(request, id):
-    s = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+
+    s = Paste.get_paste(local.db,pasteid, revid)
     if not s:
         raise NotFound
+
     form = PasteForm(request.form, prefix='paste', **{
         'title': s.title,
         'snippet': s.content,
         'language': str(s.language)
     })
 
-    if request.method=='POST' or request.method=='PUT' and form.validate():
+    error = ""
+
+    if request.method=='POST' and form.validate():
         if 'bcancel' in form.data:
             return redirect ('/%s' % s.pasteid)
-        old_revision = s.nb_revision
+        
+        old_revision = s.revid
         old_content = s.content 
         
          # add latest reviews to unchanged lines
         s.content = form.data['snippet']
         s.language = form.data['language']
 
-        s.store(local.db)
-        if s.nb_revision == old_revision:
-            return redirect ('/%s?msg=%s' % (s.pasteid, url_quote("No changes detected.")))
+        try:
+            s.store(local.db)
+        except ResourceConflict:
+            error = "ResourceConflict"
+        except:
+            error = "Unknown"
+        
+        if not error:
+            if revid:
+                redirect_url = url_for('paste/view', id=id, rev=revid)
+            else:
+                redirect_url = url_for('paste/view', id=id)
 
-        # move reviews for unchanged lines
-        Review.update_positions(local.db, s.pasteid, s.nb_revision, old_revision, unchanged)
-       
-        # warn users that paste has been updated
-        message = json.dumps({
-            'nb_revision': s.nb_revision,
-            'type': 'update'
-        })
-        send_stomp_msg(' ' + message, destination='/paste/%s' % (
-                    s.pasteid))
-
-        return redirect ('/%s' % s.pasteid)
-    return render_response('paste/edit.html', form=form, snippet=s)
+            if s.revid != old_revision:
+                # move reviews for unchanged lines
+                Review.update_positions(local.db, s.id, s.revid, old_revision, unchanged)
+                
+                # warn users that paste has been updated
+                message = json.dumps({
+                    'revision': s.revid,
+                    'type': 'update'
+                })
+                send_stomp_msg(' ' + message, destination='/paste/%s' % (id))
+            return redirect (redirect_url)
+    
+    s.pasteid = id
+    
+    return render_response('paste/edit.html', form=form, snippet=s, error=error)
 edit_snippet = login_required(edit_snippet, privacy=['private', 'public'])
 
 @login_required
 def fork_snippet(request, id):
-    s = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    error = False
+
+    s = Paste.get_paste(local.db, pasteid, revid)
+
     if not s:
         raise NotFound
+
     form = PasteForm(request.form, prefix='paste', **{
-        'title': s.title,
+        'title': s.title + ' [Forked]',
         'snippet': s.content,
         'language': str(s.language)
     })
 
         s.forked = True
         s.store(local.db)
-
-        new_paste = Paste(
+        try:
+            new_paste = Paste(
                 title=form.data['title'],
                 content=form.data['snippet'],
                 language=form.data['language'],
                 edit_code= code,
-                privacy=form.data['privacy'],
-                password=pwd,
                 fork = True,
-                fork_parent = s.pasteid,
-                forked_atrevision = s.nb_revision
-        )
-        new_paste.store(local.db)
+                fork_parent = pasteid,
+                forked_atrevision = s.revid
+            )
+            new_paste.store(local.db)
 
-        # save fork infos
-        new_fork = Fork(
-                fork_parent = s.pasteid,
-                forked_atrevision = s.nb_revision,
-                fork_id = new_paste.pasteid
-        )
-        new_fork.store(local.db)
-        return redirect('/%s' % new_paste.pasteid)
+            # save fork infos
+            new_fork = Fork(
+                fork_parent = pasteid,
+                forked_atrevision = s.revid,
+                fork_id = new_paste.id
+            )
+            new_fork.store(local.db)
+
+            # save privacy
+            p = Privacy(
+                pasteid = new_paste.id,
+                privacy = form.data['privacy'],
+                password = pwd
+            )
+            p.store(local.db)
+        
+        
+            encoded_id =  b62encode(UUID(new_paste.id).int)
+            return redirect('/%s' % encoded_id) 
+        except:
+            error = True
 
     return render_response('paste/fork.html', form=form, snippet=s)
 
 @login_required
 def view_snippet(request, id):
-    mimetypes = request.accept_mimetypes
-    s = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db,pasteid, revid)
+
     if s is None:
         raise NotFound
+
+    mimetypes = request.accept_mimetypes
     if 'application/json' in mimetypes and request.method=='PUT':
         d = json.loads(request.data)
         lang=request.values.get("lang", None)
             s.language = language
             s.store(local.db)
         except:
-            return send_json({'ok': False, 'reason': 'you should provide snippet code'})
+            return send_json({'ok': False, 'reason': 'unknown'})
         if s.revid == old_revid:
-            return send_json({'ok': False, 'reason': 'no changes detected'})
+            return send_json({'ok': False, 'reason': 'no changes'})
 
         # warn users that paste has been updated
         message = json.dumps({
-            'nb_revision': s.nb_revision,
+            'revision': s.revid,
             'type': 'update'
         })
-        send_stomp_msg(' ' + message, destination='/paste/%s' % (
-                    s.pasteid))
+
+        send_stomp_msg(' ' + message, destination='/paste/%s' % (id))
 
         return send_json({
             'ok': True,
-            'id': s.pasteid,
-            'nb_revision': s.nb_revision,
-            'url': '%s/%s' % (settings.SITE_URI, s.pasteid)
+            'id': id,
+            'revision': s.revid,
+            'url': '%s/%s' % (settings.SITE_URI, id)
         })
 
-    revid = request.values.get('rev', None)
-    if revid is not None:
-        if revid != s.nb_revision or revid != s.revid:
-            s = s.revision(local.db, revid)
         
     if 'application/json' in mimetypes:
         if s is None:
             return send_json({'ok': false})
         if request.method == 'GET':
             snippet_hash = {
-                'id': s.pasteid,
-                'nb_revision': s.nb_revision,
+                'id': id,
+                'revision': s.revid,
                 'title': s.title,
                 'snippet': s.content,
                 'language': str(s.language)
             }
             return send_json(snippet_hash)
             
-    if s is None:
-        raise NotFound
-    
     form = PasteForm(request.form, prefix='paste', **{
         'title': s.title,
         'snippet': s.content,
         'language': str(s.language)
     })
-    
-    reviews_counts = Review.get_reviews_counts(local.db, s.pasteid, s.nb_revision)
+   
+    s.pasteid = id
+
+    reviews_counts = Review.get_reviews_counts(local.db, pasteid, s.revid)
     return render_response('paste/view.html', snippet=s, form=form, rev=revid, 
             reviews_counts=reviews_counts)
  
 def delete_snippet(request, id):
-    s = Paste.get_paste(local.db, id)
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.load(local.db, pasteid)
     if s is None or not s.edit_code:
         raise NotFound
 
     if request.method == "POST" or request.method == "DELETE":
         edit_code = request.form.get('edit_code', '')
         if edit_code and make_hash(edit_code) == s.edit_code:
-            del local.db[s.id]
+            Paste.delete(local.db, pasteid)
+
             message = json.dumps({
                 'type': 'delete'
             })
             return send_json({ "ok": False })
         error = True
 
+    s.pasteid = id
+
     return render_response('paste/delete.html', snippet=s, error=error)
 
 
 def lock_snippet(request, id):
-    s = Paste.get_paste(local.db, id)
+    pasteid = UUID(int=b62decode(id)).hex
+    revid = request.values.get('rev', None)
+    s = Paste.load(local.db, pasteid)
+
     if s is None or not s.edit_code:
         raise NotFound
 
     if request.method == "POST":
         edit_code = request.form.get('edit_code', '')
         if edit_code and make_hash(edit_code) == s.edit_code:
-            s.locked = not s.locked
-            s.store(local.db)
+            locked = not s.locked
+            try:
+                Paste.lock(local.db, pasteid, locked) 
 
-            if request.is_xhr:
-                return send_json({ "ok": True })
-            return redirect("/%s" % s.pasteid)
+                if request.is_xhr:
+                    return send_json({ "ok": True })
+                return redirect(url_for("paste/view", id=id, rev=revid))
+            except:
+                pass
         if request.is_xhr:
-            return send_jsoin({ "ok": False })
+            return send_json({ "ok": False })
         error = True
     return render_response('paste/lock.html', snippet=s, error=error)
 
 @login_required  
 def view_source(request, id):
-    s = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db,pasteid, revid)
+
     if s is None:
         raise NotFound
-        
-    revid = request.values.get('rev', None)
-    if revid is not None and revid != s.revid:
-        s = s.revision(local.db, revid)
-        
-    if s is None:
-        raise NotFound
-        
+               
     return render_response('paste/view_source.html', snippet=s)
 
 
 def view_rss(request, id):
-    snippet, revisions = Paste.with_revisions(local.db, id, request.values.get('rev'))
+    pasteid = UUID(int=b62decode(id)).hex
+    snippet, revisions = Paste.with_revisions(local.db, pasteid, request.values.get('rev'))
     if snippet is None:
         raise NotFound
         
-    p = Privacy.for_paste(local.db, id)
+    p = Privacy.for_paste(local.db, pasteid)
     if p.privacy == "private":
         if p.password != make_hash(request.values.get('password', '')):
             raise Unauthorized
 
-
     feed = RssFeed(
-        title="Revisions to %s on Friendpaste" % (snippet.title and snippet.title or "snippet #%s" % snippet.pasteid),
+        title="Revisions to %s on Friendpaste" % (snippet.title and snippet.title or "snippet #%s" % id),
         description = '',
-        link = "%s/%s" % (settings.SITE_URI, snippet.pasteid)
+        link = "%s/%s" % (settings.SITE_URI, id)
     
     )
  
                     
         feed.add_entry(RssFeedEntry(
             title="%s revision %s" % (revision.title, revision.revid),
-            link= "%s/%s?rev=%s" % (settings.SITE_URI, id, revision.nb_revision),
+            link= "%s/%s?rev=%s" % (settings.SITE_URI, id, revision.revid),
             description=description,
             **{ 
                 'published': datetimestr_topython(revision.created),
 
 @login_required
 def view_revisions(request, id):
-    snippet, revisions = Paste.with_revisions(local.db, id, request.values.get('rev'))
+    pasteid = UUID(int=b62decode(id)).hex
+    snippet, revisions = Paste.with_revisions(local.db, pasteid, request.values.get('rev'))
     
     if snippet is None:
         raise NotFound
     revisions = [snippet] + revisions
     revisions_ = []
     if revisions and revisions is not None:
-        revisions_ = [rev._data for rev in revisions] 
+        for rev in revisions:
+            revision = rev._data
+            revision['pasteid'] = rev.pasteid
+            revisions_.append(revision) 
 
     if 'application/json' in request.accept_mimetypes:
         return send_json(revisions_)
 
+    snippet.pasteid = id
+
     return render_response('paste/revisions.html', snippet=snippet, revisions=revisions_)
     
     
     if request.is_xhr and request.method == 'POST':
         
         data = json.loads(request.data)
-        print data.get('language', ''),
         highlighted = shighlight(
                         data.get('paste', ''),
                         data.get('language', 'text')
 
 @login_required
 def view_rawsnippet(request, id, rev):
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db,pasteid, rev)
     paste =  Paste.get_paste(local.db, id)
-    if paste is None:
-        raise NotFound
-    s = paste.revision(local.db, rev)
     if s is None:
         raise NotFound
+    
     response = FPResponse(s.content)
     response.headers['content-type'] = 'text/plain'
     return response
 
 @login_required
 def view_original(request, id, rev):
-    snippet = Paste.get_paste(local.db, id)
-    if not snippet:
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db,pasteid, rev)
+    paste =  Paste.get_paste(local.db, id)
+    if s is None:
         raise NotFound
-    s = snippet.revision(local.db, rev)
-    if not s:
-        raise NotFound
+    
     from pygments import lexers
     lexer = lexers.get_lexer_by_name(s.language)
     response = FPResponse(s.content)
     response.headers['content-type'] = lexer.mimetypes[0]
     if lexer.filenames and len(lexer.filenames)>0:
         response.headers['content-disposition'] =  "filename=%s%s" % (
-            s.pasteid, lexer.filenames[0][1:])
+            id, lexer.filenames[0][1:])
     else:
-        response.headers['content-disposition'] =  "filename=%s.txt" % snippet.pasteid
+        response.headers['content-disposition'] =  "filename=%s.txt" % id
     return response
 
 @login_required
 def view_changeset(request, id):
-    snippet = Paste.get_paste(local.db, id)
     revid = request.values.get('rev', None)
-    if revid is not None:
-        if revid!= snippet.nb_revision or revid != snippet.revid:
-            snippet = snippet.revision(local.db, revid)
-    
+    pasteid = UUID(int=b62decode(id)).hex
+    snippet = Paste.get_paste(local.db,pasteid, revid)
+
     unidiff, tabular, previous = snippet.get_changeset(local.db)
     mimetypes = request.accept_mimetypes
     if 'application/json' in mimetypes:
         return send_json({
             'id': id, 
-            'rev': snippet.nb_revision,
+            'rev': snippet.revid,
             'changeset': unidiff
         })
 
         response.headers['content-type'] = 'text/plain'
         return response
 
+    snippet.pasteid = id
     return render_response('paste/diff.html', unidiff=unidiff, difft=tabular,
-            snippet=snippet, rev=snippet.nb_revision, old_rev=previous.nb_revision)
+            snippet=snippet, rev=snippet.revid, old_rev=previous.revid)
 
 
 @login_required
 def snippet_reviews(request, id, nb_line):
-    rev = request.values.get('rev')
-    if rev is not None:
-        s = Paste.for_revision(local.db, id, rev)
-    else:
-        s = Paste.get_paste(local.db, id)
-   
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db, pasteid, revid)
+    
     if s is None:
         return send_json({ "ok": False })
 
-    review = Review.get_line(local.db, s, nb_line)
-    
-    if review['reviews']:
-        review['reviews'].sort(lambda a,b: cmp(a['created'], b['created']))
-        review['reviews'].reverse()
-        for comment in review['reviews']:
+    s.pasteid = id
+    all_reviews = Review.get_line(local.db, s, nb_line)
+    count = len(all_reviews)
+
+    if all_reviews:
+        for comment in all_reviews:
             value = datetimestr_topython(comment['created']) 
             comment['fcreated'] = value.strftime("%a %b %d %Y at %H:%M")
 
     
-    return send_json({ "ok": True, "r": review })
+    return send_json({ "ok": True, "r": all_reviews })
 
 @login_required
 def snippet_reviews_set(request, id, nb_line):
-    rev = request.values.get('rev')
-    if rev is not None:
-        s = Paste.for_revision(local.db, id, rev)
-    else:
-        s = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db,pasteid, revid)
 
     if s is None:
         return send_json({ "ok": False })
 
-    review = Review.get_line(local.db, s, nb_line)
+    s.pasteid = id
+    all_reviews = Review.get_line(local.db, s, nb_line)
+    count = len(all_reviews)
 
     from_line = int(request.values.get('f', 0))
-    all_reviews = []
-    if review['reviews']:
-        print len(review['reviews'])
-        if len(review['reviews']) >= from_line:
-            all_reviews = review['reviews'][from_line:]
+    if all_reviews:
+        if count >= from_line:
+            all_reviews = all_reviews[:-from_line]
         else:
             all_reviews = []
     
-        all_reviews.sort(lambda a,b: cmp(a['created'], b['created']))
-        all_reviews.reverse()
         for comment in all_reviews:
             value = datetimestr_topython(comment['created']) 
             comment['fcreated'] = value.strftime("%a %b %d %Y at %H:%M")
     return send_json({ 
         "ok": True, 
         "r": all_reviews,
-        "count": len(review['reviews'])
+        "count":count 
     })
 
 @login_required
 def snippet_review(request, id, nb_line):
-    rev = request.values.get('rev')
-
-    if rev is not None:
-        s = Paste.for_revision(local.db, id, rev)
-    else:
-        s = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    s = Paste.get_paste(local.db, pasteid, revid)
 
     if s is None:
         return send_json({ "ok": False })
 
+    s.pasteid = id
     review = Review.review_for_line(local.db, s, nb_line)
 
     last_inserted = 0
             review.store(local.db)    
         else:
             review = Review(
-                    pasteid = s.pasteid,
-                    nb_revision = s.nb_revision,
+                    pasteid = pasteid,
+                    revision = s.revid,
                     nb_line = nb_line,
                     reviews = [new_review]
             )
-            review.store(local.db)
+            try:
+                review.store(local.db)
+            except:
+                return send_json({"ok": False })
 
         # warn all connected user of this addition
         message = json.dumps({
         })
 
         send_stomp_msg(' ' + message, destination='/paste/%s/%s' % (
-                    s.pasteid, s.nb_revision))
+                    id, s.revid))
 
     all_reviews = []
     if review.reviews:
 
 
 def embed(request, id):
-    p = Paste.get_paste(local.db, id)
+    revid = request.values.get('rev', None)
+    pasteid = UUID(int=b62decode(id)).hex
+    p = Paste.get_paste(local.db,pasteid, revid)
+    
     if p is None:
         raise NotFound
 
-    priv = Privacy.for_paste(local.db, id)
+    priv = Privacy.for_paste(local.db, p.id)
     if priv.privacy == "private":
         if priv.password != make_hash(request.values.get('password', '')):
             raise Unauthorized

static/css/src/layout.css

 td.nums,
 td.nums table {
     width: 30px;
-}                     =
+}                     
 #review_comment_area table {
     width: 100%;
     border-spacing: 0;

static/css/src/typography.css

     line-height: 16px;
 }
 
+form#snippet-edit,
 #snippet-edit label,
-#snippet-edit input['text'],
+#snippet-edit input[type='text'],
 #snippet-edit select,
-#snippet-edit input['password'],
-#snippet-edit {
+#snippet-edit input[type='password'] {
     font-size: 0.9em;
 }
 
     font-size: 0.8em;
 }
 
+#fpassword h2 {
+    font-size:2em;
+    padding: 0.3em;
+    margin-bottom:0.75em;
+}
 
 .comment-box h1 {font-size:1.6em;line-height:1;margin-bottom:0.5em;}
 .comment-box h2 {font-size:1.4em;margin-bottom:0.75em;}

static/js/src/friendpaste.js

             data.forEach(function(item, index) {
                 tcontent += "<tr>" +
                 "<td><time title=\"GMT\" datetime=\"" + item["updated"] + "\">"+ item["updated"] +"</time></td>" +
-                "<td class=\"rev\">rev. <a href='/" + item['pasteid'] + '?rev=' + item['nb_revision'] + "'>" + item['nb_revision'] + "</a></td>" +
-                "<td class=\"changeset\"><a href='/" + item['pasteid'] + '/changeset?rev=' + item['nb_revision'] + "'>Diff</a></td>" +
-                "<td class=\"view-rev\"><a href='/" + item['pasteid'] + '?rev=' + item['nb_revision'] + "'>View</a></td></tr>";
+                "<td class=\"rev\">rev. <a href='/" + item['pasteid'] + '?rev=' + item['revid'] + "'>" + item['revid'] + "</a></td>" +
+                "<td class=\"changeset\"><a href='/" + item['pasteid'] + '/changeset?rev=' + item['revid'] + "'>Diff</a></td>" +
+                "<td class=\"view-rev\"><a href='/" + item['pasteid'] + '?rev=' + item['revid'] + "'>View</a></td></tr>";
                 
             });
             tcontent = '<table class="revisionstable">'+tcontent+'</table>';

static/js/src/review.js

                 this._notify(txt);
             }
         } else if (message.type == 'update') {
-             if (message.nb_revision == revid) 
+             if (message.revision == revid) 
                 return;
              var txt = 'Past has been updated to <a href="/'+snippet_id+
-                 '?rev='+message.nb_revision+'">revision '+
-                 message.nb_revision + '</a>';
+                 '?rev='+message.revision+'">revision '+
+                 message.revision + '</a>';
             this._notify(txt);
         } else if (message.type == 'delete') { 
             var txt = 'Paste has been deleted.';
             method: "GET",
             success: function(data) {
                 if (data['ok']) {
-                    if (data['r'].reviews) {
-                        
+                    if (data['r']) {
                         var ul = document.createElement('ul');
-                        for(var i=0; i < data['r'].reviews.length; i++) {
+                        for(var i=0; i < data['r'].length; i++) {
                             var li = document.createElement('li');
                             var time = document.createElement('time');
                             time.title = 'GMT';
-                            time.setAttribute('datetime', data['r'].reviews[i]['created']);
-                            time.textContent = data['r'].reviews[i]['fcreated'];
-                            li.innerHTML = self.converter.makeHtml(data['r'].reviews[i]['comment']);
+                            time.setAttribute('datetime', data['r'][i]['created']);
+                            time.textContent = data['r'][i]['fcreated'];
+                            li.innerHTML = self.converter.makeHtml(data['r'][i]['comment']);
                             var meta = document.createElement('div');
                             meta.classList.add('metadata');
-                            meta.appendChild(document.createTextNode(data['r'].reviews[i]['nickname']));
+                            meta.appendChild(document.createTextNode(data['r'][i]['nickname']));
                             meta.appendChild(time);
                             li.appendChild(meta)
                             ul.appendChild(li);
                         reviews_list.classList.remove('hidden');
                         localizeDates();
                     }
+                } else {
+                    remove(loading);
+                    reviews_list.appendChild(document.createTextNode("No reviews yet."))
+                    reviews_list.classList.remove('hidden');
                 }
             }
         });

templates/paste/edit.html

 <section id="edit">
 
     <form id="snippet-edit" name="fpaste" action="/{{ snippet.pasteid }}/edit" method="post">
-        {% if form.errors %}
+        {% if form.errors or error %}
         <h2 class="errors">Something was wrong!</h2>
         <ul class="errors">
+            {% if error == "ResourceConflict" %}
+            <li><strong>Update conflict!</strong> Someone has updated the paste since. Do you want to retry or see the <a href="{{ url_for('paste/view', id={{ snippey.pasteid }}) }}">latest revision</a> ? To retry click on "save changes" button.</li>
+            {% else %}
+            <li><strong>Unknown error %}</strong></li>
+            {% endif %}
             {% if form.snippet.errors %}
             <li><strong>Paste text:</strong><ul>{% for e in form.snippet.errors %}<li>{{ e }}</li>{% endfor %}</ul></li>
             {% endif %}
+
         </ul>
         {% endif %}
         <h2>Edit <a href="/{{ snippet.pasteid }}">{% if snippet.title %}{{
     </form>
 </section>
 {% endblock %}
+
+{% block script %}
+{% if DEBUG %}
+<script type="text/javascript" src="/static/js/lib/showdown.js"></script>
+<script type="text/javascript" src="/static/js/src/resizeable.js"></script>
+<script type="text/javascript" src="/static/js/src/editor.js"></script>
+<script type="text/javascript" src="/static/js/src/friendpaste.js"></script>
+{% else %}
+<script type="text/javascript"
+    src="/static/js/friendpaste.js?20081215"></script>
+{% endif %}
+<script type="text/javascript">
+     if(!base2.detect("webkit"))
+        new Resizeable("#paste_snippet");
+
+    // init source code editor
+    new Editor("#paste_snippet");
+</script>
+{% endblock %}

templates/paste/fork.html

      class="errors"{% endif %}>{{ form.snippet(cols=70,rows=10,class="resizable") }}</li>
             <li>{{ form.language.label }}{{ form.language }}</li>
             <li>{{ form.title.label }}{{ form.title }}</li>
-            <li>{{ form.code.label }}{{ form.code(autocomplete="off")
-                }}</li>
+             <li>{{ form.code.label }}{{ form.code(autocomplete="off")
+                }}<p class="tip">Removal/Lock code is a password to let you
+        <strong>remove</strong> or 
+        <strong>lock edit</strong> of paste you upload.</p></li>
+            <li><span class="privacy{% if form.data['privacy'] != "open"%} setPrivacy{% endif %}">Privacy settings</span>
+            <ol id="privacy"{% if form.data['privacy'] == "open"%} class="hidden"{% endif %}>
+                <li>{{ form.privacy.label }}{{ form.privacy }}</li>
+                    <li{% if form.privacy.errors %}
+            class="errors"{% endif %}>{{ form.password.label }}{{ form.password }}</li>
+                </ol>
+            </li>
             <li class="ccode"><input type="submit" name="psubmit"
             value="Submit paste" /></li>
         </ol>
-        <p class="tip">Removal/Lock code is a password to let you
-        <strong>remove</strong> or 
-        <strong>lock edit</strong> of paste you upload.</p>
+        
     </form>
 
 </section>
 {% block script %}
 {% if DEBUG %}
 <script type="text/javascript" src="/static/js/src/resizeable.js"></script>
+<script type="text/javascript" src="/static/js/src/friendpaste.js"></script>
 <script type="text/javascript" src="/static/js/src/editor.js"></script>
-<script type="text/javascript" src="/static/js/src/friendpaste.js"></script>
+
 {% else %}
 <script type="text/javascript"
     src="/static/js/friendpaste.js?200811231151"></script>
 {% endif %}
 
 <script type="text/javascript">
-    if(!base2.detect("webkit"))
-        new Resizeable("#paste_snippet");
+    new Home(); 
 
-    // init source code editor
-    new Editor("#paste_snippet");
 
-    document.querySelector("#paste_snippet").focus();
 </script>
 {% endblock %}

templates/paste/index.html

 {% block content %}
 <section id="new_snippet">
     <form id="snippet-edit" name="fpaste" action="/" method="post">
-        {% if form.errors %}
+        {% if form.errors or error %}
         <h2 class="errors">Something was wrong!</h2>
         <ul class="errors">
+            {% if error %}
+            <li>Something happend while saving data. Please contact the admin and/or retry later.</li>
+            {% endif %}
             {% if form.snippet.errors %}
             <li><strong>Paste text:</strong><ul>{% for e in form.snippet.errors %}<li>{{ e }}</li>{% endfor %}</ul></li>
             {% endif %}

templates/paste/login.html

 {% extends "base.html" %}
 
 {% block title %}
-    Login to this paste
+Login to paste #{{ pasteid }}
 {% endblock %}
 
 {% block header %}{% endblock %}
 <section id="new_snippet">
     <form name="fpassword" id="fpassword" action="{{ url_for('login',id=pasteid) }}" method="post">
         <input type="hidden" name="next" value="{{ next }}" />
-        <h2>Please enter a pasword for the paste #{{ pasteid }}</h2>
+        <h2>Please enter a pasword the paste</h2>
         {% if privacy == 'private' %}
         <h4>Password is required to view and edit paste.</h4>
         {% endif %}

templates/paste/revisions.html

         <tr>
             <td class="since">{{ rev['updated']|timesince }}</td>
             <td class="rev">rev. <a href="/{{ snippet.pasteid }}?rev={{
-                    rev['nb_revision'] }}">{{ rev['nb_revision'] }}</a></td>
+                    rev['revid'] }}">{{ rev['revid'] }}</a></td>
             <td class="changeset"><a href="/{{ snippet.pasteid
-                    }}/changeset?rev={{ rev['nb_revision'] }}">Diff</a></td>
+                    }}/changeset?rev={{ rev['revid'] }}">Diff</a></td>
             <td class="view-rev"><a href="/{{ snippet.pasteid }}?rev={{
-                    rev['nb_revision'] }}">View</a></td>
+                    rev['revid'] }}">View</a></td>
         </tr>
     {% endfor %}
     </table>

templates/paste/view.html

 
 {% block auth %}
     {% if privacy != 'open' and not can_edit %}
-    <div class="authBox"><a href="{{ url_for('login', id=snippet.pasteid,rev=snippet.nb_revision) }}&next={{ url_for('paste/view', id=snippet.pasteid, rev=snippet.nb_revision) }}">Login to edit</a></div>
+    <div class="authBox"><a href="{{ url_for('login', id=snippet.pasteid,rev=snippet.revid) }}&next={{ url_for('paste/view', id=snippet.pasteid, rev=snippet.revid) }}">Login to edit</a></div>
     {% endif %}
 
     {% if privacy != 'open' and can_edit %}
-    <div class="authBox"><a href="{{ url_for('logout', id=snippet.pasteid) }}?next={{ url_for('paste/view', id=snippet.pasteid, rev=snippet.nb_revision) }}">Logout from this paste</a></div>
+    <div class="authBox"><a href="{{ url_for('logout', id=snippet.pasteid) }}?next={{ url_for('paste/view', id=snippet.pasteid, rev=snippet.revid) }}">Logout from this paste</a></div>
     {% endif %}
 
 {% endblock %}
 
 <script type="text/javascript">
 snippet_id = "{{ snippet.pasteid }}";
-revid = "{{ snippet.nb_revision }}";
+revid = "{{ snippet.revid }}";
 </script>
 
 <script>document.domain=document.domain;</script>
     <article id="snippet">
         <header>
             <div id="snippet_title"> 
-                <h2><a href="/{{ snippet.pasteid }}" class="root">{{
-                    snippet.pasteid }}</a>&nbsp;/&nbsp;{% if
+                <h2><a href="/{{ snippet.pasteid }}" class="root">{% if
             snippet.title %}{{ snippet.title }}{% else %}No title{%
-                endif %}</h2>
+                endif %}</a></h2>
                 {% if snippet.edit_code %}
                 <div id="snippet_actions">
                     <ul>
             
             <div id="info">
                 <p>Revision <a href="/{{ snippet.pasteid }}?rev={{
-                    snippet.nb_revision }}">{{ snippet.nb_revision
+                    snippet.revid }}">{{ snippet.revid
                     }}</a> (<time title="GMT" datetime="{{
                 snippet.updated|rfc3339 }}">{{
                 snippet.updated|formatdatetime }}</time>) - <a href="{{
                     url_for('paste/changeset', id=snippet.pasteid,
-                    rev=snippet.nb_revision) }}">Diff</a>
+                    rev=snippet.revid) }}">Diff</a>
                 </p> 
                 
                 <form class="embed">
                             <input type="submit" class="e" value="Edit paste" />
                         </form>
                         {% else %}
+                        {% if not snippet.locked %}
                         <form name="flogin" action="/{{ snippet.pasteid }}/login" method="get">
                             <input type="hidden" name="next" value="/{{ snippet.pasteid }}#edit" />
                             <input type="submit" value="Login to edit" />
                         </form>
                         {% endif %}
+                        {% endif %}
 
                     </li>
                     <li>
                 </ul>
             </div>
             <p id="dl"><strong>View in other formats:</strong><br /><a
-                href="/{{ snippet.pasteid }}_{{ snippet.nb_revision }}/raw">raw
+                href="/{{ snippet.pasteid }}_{{ snippet.revid }}/raw">raw
                 format</a> | <a href="/{{ snippet.pasteid }}_{{
-                snippet.nb_revision }}/original">download source</a></p>
+                snippet.revid }}/original">download source</a></p>
         </footer>
     </article>
 </section>
 {% endif %}
 
 <section id="revisions" class="hidden">
-    <h2><a href="/{{ snippet.pasteid }}" class="root">{{ snippet.pasteid }}</a>&nbsp;/&nbsp;{% if snippet.title %}{{ snippet.title }}{% else %}No title{% endif %}</h2>
+<h2><a href="/{{ snippet.pasteid }}" class="root">{% if snippet.title %}{{ snippet.title }}{% else %}No title{% endif %}</a></h2>
     <p>last change {{ snippet.updated|datetimeformat }} (UTC)</p>
     <h3>Older revisions</h3>
     <div class="pasteHistory"></div>