Fixed several edge cases with re-ranking notes during a reorder. Now updating several notes' ranks on every reorder instead of making successively more precise fractional ranks.
This commit is contained in:
parent
5446eeec5d
commit
a8af8b3fb3
|
@ -391,6 +391,13 @@ class Database( object ):
|
|||
|
||||
cache.delete( obj.cache_key )
|
||||
|
||||
def uncache_many( self, Object_type, obj_ids ):
|
||||
cache = self.__get_cache_connection()
|
||||
if not cache: return
|
||||
|
||||
for obj_id in obj_ids:
|
||||
cache.delete( Persistent.make_cache_key( Object_type, obj_id ) )
|
||||
|
||||
@staticmethod
|
||||
def generate_id():
|
||||
int_id = random.getrandbits( Database.ID_BITS )
|
||||
|
|
|
@ -726,12 +726,26 @@ class Notebooks( object ):
|
|||
position_before = None
|
||||
position_after = None
|
||||
|
||||
def calculate_rank( position_after, position_before ):
|
||||
def update_rank( position_after, position_before ):
|
||||
after_note = position_after and self.__database.load( Note, position_after ) or None
|
||||
before_note = position_before and self.__database.load( Note, position_before ) or None
|
||||
|
||||
if after_note and before_note:
|
||||
return ( float( after_note.rank ) + float( before_note.rank ) ) / 2.0
|
||||
new_rank = float( after_note.rank ) + 1.0
|
||||
|
||||
# if necessary, increment the rank of all subsequent notes to make "room" for this note
|
||||
if new_rank >= before_note.rank:
|
||||
# clear the cache of before_note and all notes with subsequent rank
|
||||
self.__database.uncache_many(
|
||||
Note,
|
||||
self.__database.select_many(
|
||||
unicode,
|
||||
notebook.sql_load_note_ids_starting_from_rank( before_note.rank )
|
||||
)
|
||||
)
|
||||
self.__database.execute( notebook.sql_increment_rank( before_note.rank ), commit = False )
|
||||
|
||||
return new_rank
|
||||
elif after_note:
|
||||
return float( after_note.rank ) + 1.0
|
||||
elif before_note:
|
||||
|
@ -750,7 +764,7 @@ class Notebooks( object ):
|
|||
note.startup = startup
|
||||
|
||||
if position_after or position_before:
|
||||
note.rank = calculate_rank( position_after, position_before )
|
||||
note.rank = update_rank( position_after, position_before )
|
||||
elif note.rank is None:
|
||||
note.rank = self.__database.select_one( float, notebook.sql_highest_note_rank() ) + 1
|
||||
|
||||
|
@ -783,7 +797,7 @@ class Notebooks( object ):
|
|||
# otherwise, create a new note
|
||||
else:
|
||||
if position_after or position_before:
|
||||
rank = calculate_rank( position_after, position_before )
|
||||
rank = update_rank( position_after, position_before )
|
||||
else:
|
||||
rank = self.__database.select_one( float, notebook.sql_highest_note_rank() ) + 1
|
||||
|
||||
|
|
|
@ -1987,7 +1987,7 @@ class Test_notebooks( Test_controller ):
|
|||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes > 0
|
||||
assert result[ "storage_bytes" ] == user.storage_bytes
|
||||
assert result[ "rank" ] == 0.5
|
||||
assert result[ "rank" ] == 1.0
|
||||
|
||||
# make sure the old title can no longer be loaded
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -2011,7 +2011,7 @@ class Test_notebooks( Test_controller ):
|
|||
assert note.contents == new_note_contents
|
||||
assert note.startup == False
|
||||
assert note.user_id == self.user.object_id
|
||||
assert note.rank == 0.5
|
||||
assert note.rank == 1.0
|
||||
|
||||
# make sure that the correct revisions are returned and are in chronological order
|
||||
result = self.http_post( "/notebooks/load_note_revisions/", dict(
|
||||
|
@ -2029,6 +2029,15 @@ class Test_notebooks( Test_controller ):
|
|||
assert revisions[ 2 ].user_id == self.user.object_id
|
||||
assert revisions[ 2 ].username == self.username
|
||||
|
||||
# the position_before note should have its rank incremented to make way for the new note
|
||||
result = self.http_post( "/notebooks/load_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = before_note_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
note = result[ "note" ]
|
||||
assert note.rank == 2.0
|
||||
|
||||
def test_save_note_in_notebook_with_read_write_for_own_notes( self, after_note_id = None, before_note_id = None ):
|
||||
self.login()
|
||||
|
||||
|
@ -2914,7 +2923,7 @@ class Test_notebooks( Test_controller ):
|
|||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes > 0
|
||||
assert result[ "storage_bytes" ] == user.storage_bytes
|
||||
assert result[ "rank" ] == 0.5
|
||||
assert result[ "rank" ] == 1.0
|
||||
|
||||
# make sure the new title is now loadable
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
|
@ -2929,7 +2938,108 @@ class Test_notebooks( Test_controller ):
|
|||
assert note.contents == new_note.contents
|
||||
assert note.startup == False
|
||||
assert note.user_id == self.user.object_id
|
||||
assert note.rank == 0.5
|
||||
assert note.rank == 1.0
|
||||
|
||||
# the position_before note should have its rank incremented to make way for the new note
|
||||
result = self.http_post( "/notebooks/load_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = before_note_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
note = result[ "note" ]
|
||||
assert note.rank == 2.0
|
||||
|
||||
def test_save_new_note_with_position_after_and_before_and_gap( self ):
|
||||
self.login()
|
||||
|
||||
temp_note_id = u"someid0"
|
||||
new_note_contents = u"<h3>temp</h3>"
|
||||
result = self.http_post( "/notebooks/save_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = temp_note_id,
|
||||
contents = new_note_contents,
|
||||
startup = False,
|
||||
previous_revision = None,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ "rank" ] == 0.0
|
||||
|
||||
after_note_id = u"someid1"
|
||||
new_note_contents = u"<h3>after this</h3>"
|
||||
result = self.http_post( "/notebooks/save_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = after_note_id,
|
||||
contents = new_note_contents,
|
||||
startup = False,
|
||||
previous_revision = None,
|
||||
position_before = temp_note_id
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ "rank" ] == -1.0
|
||||
|
||||
before_note_id = u"someid2"
|
||||
new_note_contents = u"<h3>before this</h3>"
|
||||
result = self.http_post( "/notebooks/save_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = before_note_id,
|
||||
contents = new_note_contents,
|
||||
startup = False,
|
||||
previous_revision = None,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ "rank" ] == 1.0
|
||||
|
||||
self.http_post( "/notebooks/delete_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = temp_note_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
# save a completely new note
|
||||
new_note = Note.create( "55", u"<h3>newest title</h3>foo" )
|
||||
previous_revision = new_note.revision
|
||||
result = self.http_post( "/notebooks/save_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = new_note.object_id,
|
||||
contents = new_note.contents,
|
||||
startup = False,
|
||||
previous_revision = None,
|
||||
position_after = after_note_id,
|
||||
position_before = before_note_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ "new_revision" ]
|
||||
assert result[ "new_revision" ] != previous_revision
|
||||
assert result[ "new_revision" ].user_id == self.user.object_id
|
||||
assert result[ "new_revision" ].username == self.username
|
||||
assert result[ "previous_revision" ] == None
|
||||
user = self.database.load( User, self.user.object_id )
|
||||
assert user.storage_bytes > 0
|
||||
assert result[ "storage_bytes" ] == user.storage_bytes
|
||||
assert result[ "rank" ] == 0.0
|
||||
|
||||
# make sure the new title is now loadable
|
||||
result = self.http_post( "/notebooks/load_note_by_title/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_title = new_note.title,
|
||||
), session_id = self.session_id )
|
||||
|
||||
note = result[ "note" ]
|
||||
|
||||
assert note.object_id == new_note.object_id
|
||||
assert note.title == new_note.title
|
||||
assert note.contents == new_note.contents
|
||||
assert note.startup == False
|
||||
assert note.user_id == self.user.object_id
|
||||
assert note.rank == 0.0
|
||||
|
||||
# the position_before note should have its rank incremented to make way for the new note
|
||||
result = self.http_post( "/notebooks/load_note/", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
note_id = before_note_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
note = result[ "note" ]
|
||||
assert note.rank == 1.0
|
||||
|
||||
def test_save_new_note_in_notebook_with_read_write_for_own_notes( self, after_note_id = None, before_note_id = None ):
|
||||
self.login()
|
||||
|
|
|
@ -2,6 +2,8 @@ import re
|
|||
from copy import copy
|
||||
from Note import Note
|
||||
from Persistent import Persistent, quote, quote_fuzzy
|
||||
from datetime import datetime
|
||||
from pytz import utc
|
||||
|
||||
|
||||
class Notebook( Persistent ):
|
||||
|
@ -357,6 +359,41 @@ class Notebook( Persistent ):
|
|||
order by tag.name;
|
||||
""" % ( quote( self.object_id ), quote( user_id ) )
|
||||
|
||||
def sql_load_note_ids_starting_from_rank( self, start_note_rank ):
|
||||
"""
|
||||
Return a SQL string to load a list of all the note ids with rank greater than or equal to the
|
||||
given rank.
|
||||
"""
|
||||
return \
|
||||
"""
|
||||
select
|
||||
id
|
||||
from
|
||||
note_current
|
||||
where
|
||||
notebook_id = %s and
|
||||
rank is not null and
|
||||
rank >= %s;
|
||||
""" % ( quote( self.object_id ), start_note_rank )
|
||||
|
||||
def sql_increment_rank( self, start_note_rank ):
|
||||
"""
|
||||
Return a SQL string to increment the rank for every note in this notebook (in rank order)
|
||||
starting from the given note rank. Notes before the given note rank are not updated.
|
||||
"""
|
||||
return \
|
||||
"""
|
||||
update
|
||||
note_current
|
||||
set
|
||||
rank = rank + 1,
|
||||
revision = %s
|
||||
where
|
||||
notebook_id = %s and
|
||||
rank is not null and
|
||||
rank >= %s;
|
||||
""" % ( quote( datetime.now( tz = utc ) ), quote( self.object_id ), start_note_rank )
|
||||
|
||||
def to_dict( self ):
|
||||
d = Persistent.to_dict( self )
|
||||
|
||||
|
|
|
@ -332,8 +332,12 @@ Editor.prototype.add_selection_bookmark = function () {
|
|||
// if the current range is not within this editor's static note div, then bail
|
||||
if ( range.startContainer == document || range.endContainer == document )
|
||||
return null;
|
||||
try {
|
||||
if ( !isChildNode( range.startContainer.parentNode, this.div ) || !isChildNode( range.endContainer.parentNode, this.div ) )
|
||||
return null;
|
||||
} catch ( e ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// mark the nodes that are start and end containers for the current range. we have to mark the
|
||||
// parent node instead of the start/end container itself, because text nodes can't have classes
|
||||
|
|
|
@ -1046,6 +1046,16 @@ Wiki.prototype.editor_focused = function ( editor, synchronous, remove_empty ) {
|
|||
|
||||
Wiki.prototype.editor_moved = function ( editor, position_after, position_before ) {
|
||||
this.save_editor( editor, false, null, null, null, position_after, position_before );
|
||||
|
||||
// reset the revision for each open editor. this is because the server is updating the revisions
|
||||
// on the server while reordering the ntoes. and we don't want to have a stale idea of what the
|
||||
// current revision is for a given editor
|
||||
var divs = getElementsByTagAndClassName( "div", "static_note_div" );
|
||||
for ( var i in divs ) {
|
||||
var editor = divs[ i ].editor;
|
||||
editor.revision = null;
|
||||
editor.user_revisions = new Array();
|
||||
}
|
||||
}
|
||||
|
||||
Wiki.prototype.make_byline = function ( username, creation, note_id ) {
|
||||
|
@ -1760,7 +1770,8 @@ Wiki.prototype.update_editor_revisions = function ( result, editor ) {
|
|||
|
||||
// if the server's idea of the previous revision doesn't match the client's, then someone has
|
||||
// gone behind our back and saved the editor's note from another window
|
||||
if ( result.previous_revision && result.previous_revision.revision != client_previous_revision ) {
|
||||
if ( result.previous_revision && client_previous_revision &&
|
||||
result.previous_revision.revision != client_previous_revision ) {
|
||||
var compare_button = createDOM( "input", {
|
||||
"type": "button",
|
||||
"class": "message_button",
|
||||
|
|
Reference in New Issue