witten
/
luminotes
Archived
1
0
Fork 0

Added ability to reorder notebooks on the right side of the page.

Need to complete unit tests for controller.Notebooks.move_up() and move_down().
This commit is contained in:
Dan Helfman 2008-03-15 03:04:59 +00:00
parent 68fe84b707
commit 94a51889f9
15 changed files with 414 additions and 49 deletions

3
NEWS
View File

@ -1,3 +1,6 @@
1.2.14: March ??, 2008
* Added ability to reorder notebooks on the right side of the page.
1.2.13: March 11, 2008
* When the "all notes" note is the only note open, it now actually hides when
the "hide" button is clicked.

View File

@ -505,7 +505,7 @@ class Notebooks( object ):
note.startup = startup
if startup:
if note.rank is None:
note.rank = self.__database.select_one( float, notebook.sql_highest_rank() ) + 1
note.rank = self.__database.select_one( float, notebook.sql_highest_note_rank() ) + 1
else:
note.rank = None
note.user_id = user.object_id
@ -538,7 +538,7 @@ class Notebooks( object ):
# otherwise, create a new note
else:
if startup:
rank = self.__database.select_one( float, notebook.sql_highest_rank() ) + 1
rank = self.__database.select_one( float, notebook.sql_highest_note_rank() ) + 1
else:
rank = None
@ -875,7 +875,8 @@ class Notebooks( object ):
self.__database.save( notebook, commit = False )
# record the fact that the user has access to their new notebook
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True, owner = True ), commit = False )
rank = self.__database.select_one( float, user.sql_highest_notebook_rank() ) + 1
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True, owner = True, rank = rank ), commit = False )
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True, owner = True ), commit = False )
if commit:
@ -1071,6 +1072,149 @@ class Notebooks( object ):
redirect = u"/notebooks/%s" % notebook.object_id,
)
@expose( view = Json )
@grab_user_id
@validate(
notebook_id = Valid_id(),
user_id = Valid_id( none_okay = True ),
)
def move_up( self, notebook_id, user_id ):
"""
Reorder the user's notebooks by moving the given notebook up by one. If the notebook is already
first, then wrap it around to be the last notebook.
@type notebook_id: unicode
@param notebook_id: id of notebook to move up
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype json dict
@return {}
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
if not self.__users.check_access( user_id, notebook_id ):
raise Access_error()
user = self.__database.load( User, user_id )
if not user:
raise Access_error()
# load the notebooks to which this user has access
notebooks = self.__database.select_many(
Notebook,
user.sql_load_notebooks( parents_only = True, undeleted_only = True ),
)
if not notebooks:
raise Access_error()
# find the given notebook and the one previous to it
previous_notebook = None
current_notebook = None
for notebook in notebooks:
if notebook.object_id == notebook_id:
current_notebook = notebook
break
previous_notebook = notebook
if current_notebook is None:
raise Access_error()
# if there is no previous notebook, then the current notebook is first. so, move it after the
# last notebook
if previous_notebook is None:
last_notebook = notebooks[ -1 ]
self.__database.execute(
user.sql_update_notebook_rank( current_notebook.object_id, last_notebook.rank + 1 ),
commit = False,
)
# otherwise, save the current and previous notebooks back to the database with swapped ranks
else:
self.__database.execute(
user.sql_update_notebook_rank( current_notebook.object_id, previous_notebook.rank ),
commit = False,
)
self.__database.execute(
user.sql_update_notebook_rank( previous_notebook.object_id, current_notebook.rank ),
commit = False,
)
self.__database.commit()
return dict()
@expose( view = Json )
@grab_user_id
@validate(
notebook_id = Valid_id(),
user_id = Valid_id( none_okay = True ),
)
def move_down( self, notebook_id, user_id ):
"""
Reorder the user's notebooks by moving the given notebook down by one. If the notebook is
already last, then wrap it around to be the first notebook.
@type notebook_id: unicode
@param notebook_id: id of notebook to move down
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype json dict
@return {}
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
if not self.__users.check_access( user_id, notebook_id ):
raise Access_error()
user = self.__database.load( User, user_id )
if not user:
raise Access_error()
# load the notebooks to which this user has access
notebooks = self.__database.select_many(
Notebook,
user.sql_load_notebooks( parents_only = True, undeleted_only = True ),
)
if not notebooks:
raise Access_error()
# find the given notebook and the one after it
current_notebook = None
next_notebook = None
for notebook in notebooks:
if notebook.object_id == notebook_id:
current_notebook = notebook
elif current_notebook:
next_notebook = notebook
break
if current_notebook is None:
raise Access_error()
# if there is no next notebook, then the current notebook is last. so, move it before the
# first notebook
if next_notebook is None:
first_notebook = notebooks[ 0 ]
self.__database.execute(
user.sql_update_notebook_rank( current_notebook.object_id, first_notebook.rank - 1 ),
commit = False,
)
# otherwise, save the current and next notebooks back to the database with swapped ranks
else:
self.__database.execute(
user.sql_update_notebook_rank( current_notebook.object_id, next_notebook.rank ),
commit = False,
)
self.__database.execute(
user.sql_update_notebook_rank( next_notebook.object_id, current_notebook.rank ),
commit = False,
)
self.__database.commit()
return dict()
def load_recent_notes( self, notebook_id, start = 0, count = 10, user_id = None ):
"""
Provide the information necessary to display the page for a particular notebook's most recent

View File

@ -262,7 +262,7 @@ class Users( object ):
self.__database.save( user, commit = False )
# record the fact that the new user has access to their new notebook
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True, owner = True ), commit = False )
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True, owner = True, rank = 0 ), commit = False )
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True, owner = True ), commit = False )
self.__database.commit()
@ -335,7 +335,7 @@ class Users( object ):
self.__database.save( user, commit = False )
# record the fact that the new user has access to their new notebook
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True, owner = True ), commit = False )
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True, owner = True, rank = 0 ), commit = False )
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True, owner = True ), commit = False )
self.__database.commit()
@ -966,7 +966,8 @@ class Users( object ):
# if the user doesn't already have access to this notebook, then grant access
if not self.__database.select_one( bool, user.sql_has_access( notebook.object_id ) ):
self.__database.execute( user.sql_save_notebook( notebook.object_id, invite.read_write, invite.owner ), commit = False )
rank = self.__database.select_one( float, user.sql_highest_notebook_rank() ) + 1
self.__database.execute( user.sql_save_notebook( notebook.object_id, invite.read_write, invite.owner, rank = rank ), commit = False )
# the same goes for the trash notebook
if not self.__database.select_one( bool, user.sql_has_access( notebook.trash_id ) ):

View File

@ -41,14 +41,14 @@ class Test_controller( object ):
# SQL-returning methods in User, Note, and Notebook to return functions that manipulate data in
# Stub_database directly instead. This is all a little fragile, but it's better than relying on
# the presence of a real database for unit tests.
def sql_save_notebook( self, notebook_id, read_write, owner, database ):
def sql_save_notebook( self, notebook_id, read_write, owner, rank, database ):
if self.object_id in database.user_notebook:
database.user_notebook[ self.object_id ].append( ( notebook_id, read_write, owner ) )
database.user_notebook[ self.object_id ].append( ( notebook_id, read_write, owner, rank ) )
else:
database.user_notebook[ self.object_id ] = [ ( notebook_id, read_write, owner ) ]
database.user_notebook[ self.object_id ] = [ ( notebook_id, read_write, owner, rank ) ]
User.sql_save_notebook = lambda self, notebook_id, read_write = False, owner = False: \
lambda database: sql_save_notebook( self, notebook_id, read_write, owner, database )
User.sql_save_notebook = lambda self, notebook_id, read_write = False, owner = False, rank = None: \
lambda database: sql_save_notebook( self, notebook_id, read_write, owner, rank, database )
def sql_remove_notebook( self, notebook_id, database ):
if self.object_id in database.user_notebook:
@ -66,10 +66,11 @@ class Test_controller( object ):
if not notebook_infos: return []
for notebook_info in notebook_infos:
( notebook_id, notebook_read_write, owner ) = notebook_info
( notebook_id, notebook_read_write, owner, rank ) = notebook_info
notebook = database.objects.get( notebook_id )[ -1 ]
notebook.read_write = notebook_read_write
notebook.owner = owner
notebook.rank = rank
if parents_only and notebook.trash_id is None:
continue
if undeleted_only and notebook.deleted is True:
@ -118,7 +119,7 @@ class Test_controller( object ):
def sql_has_access( self, notebook_id, read_write, owner, database ):
for ( user_id, notebook_infos ) in database.user_notebook.items():
for notebook_info in notebook_infos:
( db_notebook_id, db_read_write, db_owner ) = notebook_info
( db_notebook_id, db_read_write, db_owner, rank ) = notebook_info
if self.object_id == user_id and notebook_id == db_notebook_id:
if read_write is True and db_read_write is False:
@ -135,23 +136,53 @@ class Test_controller( object ):
def sql_update_access( self, notebook_id, read_write, owner, database ):
for ( user_id, notebook_infos ) in database.user_notebook.items():
for notebook_info in notebook_infos:
( db_notebook_id, db_read_write, db_owner ) = notebook_info
( db_notebook_id, db_read_write, db_owner, rank ) = notebook_info
if self.object_id == user_id and notebook_id == db_notebook_id:
notebook_infos_copy = list( notebook_infos )
notebook_infos_copy.remove( notebook_info )
notebook_infos_copy.append( ( notebook_id, read_write, owner ) )
notebook_infos_copy.append( ( notebook_id, read_write, owner, rank ) )
database.user_notebook[ user_id ] = notebook_infos_copy
User.sql_update_access = lambda self, notebook_id, read_write = False, owner = False: \
lambda database: sql_update_access( self, notebook_id, read_write, owner, database )
def sql_update_notebook_rank( self, notebook_id, rank, database ):
max_rank = -1
for ( user_id, notebook_infos ) in database.user_notebook.items():
for notebook_info in notebook_infos:
( db_notebook_id, db_read_write, db_owner, db_rank ) = notebook_info
if self.object_id == user_id and notebook_id == db_notebook_id:
notebook_infos_copy = list( notebook_infos )
notebook_infos_copy.remove( notebook_info )
notebook_infos_copy.append( ( db_notebook_id, db_read_write, db_owner, rank ) )
database.user_notebook[ user_id ] = notebook_infos_copy
User.sql_update_notebook_rank = lambda self, notebook_id, rank: \
lambda database: sql_update_notebook_rank( self, notebook_id, rank, database )
def sql_highest_notebook_rank( self, database ):
max_rank = -1
for ( user_id, notebook_infos ) in database.user_notebook.items():
for notebook_info in notebook_infos:
( db_notebook_id, db_read_write, db_owner, db_rank ) = notebook_info
if self.object_id == user_id and db_rank > max_rank:
max_rank = db_rank
return max_rank
User.sql_highest_notebook_rank = lambda self: \
lambda database: sql_highest_notebook_rank( self, database )
def sql_revoke_invite_access( notebook_id, trash_id, email_address, database ):
invites = []
for ( user_id, notebook_infos ) in database.user_notebook.items():
for notebook_info in list( notebook_infos ):
( db_notebook_id, read_write, owner ) = notebook_info
( db_notebook_id, read_write, owner, rank ) = notebook_info
if db_notebook_id not in ( notebook_id, trash_id ): continue
for ( object_id, obj_list ) in database.objects.items():
obj = obj_list[ -1 ]
@ -255,7 +286,7 @@ class Test_controller( object ):
Notebook.sql_search_notes = lambda self, search_text: \
lambda database: sql_search_notes( self, search_text, database )
def sql_highest_rank( self, database ):
def sql_highest_note_rank( self, database ):
max_rank = -1
for ( object_id, obj_list ) in database.objects.items():
@ -265,8 +296,8 @@ class Test_controller( object ):
return max_rank
Notebook.sql_highest_rank = lambda self: \
lambda database: sql_highest_rank( self, database )
Notebook.sql_highest_note_rank = lambda self: \
lambda database: sql_highest_note_rank( self, database )
def sql_count_notes( self, database ):
count = 0

View File

@ -54,12 +54,12 @@ class Test_notebooks( Test_controller ):
self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"anon_notebook", user_id = user_id )
self.database.save( self.anon_notebook, commit = False )
self.database.execute( self.user.sql_save_notebook( self.notebook.object_id, read_write = True, owner = True ) )
self.database.execute( self.user.sql_save_notebook( self.notebook.trash_id, read_write = True, owner = True ) )
self.database.execute( self.user.sql_save_notebook( self.notebook.object_id, read_write = True, owner = True, rank = 0 ) )
self.database.execute( self.user.sql_save_notebook( self.notebook.trash_id, read_write = True, owner = True, rank = 0 ) )
self.database.execute( self.user.sql_save_notebook( self.anon_notebook.object_id, read_write = False, owner = False ) )
self.database.execute( self.user2.sql_save_notebook( self.notebook.object_id, read_write = True, owner = False ) )
self.database.execute( self.user2.sql_save_notebook( self.notebook.trash_id, read_write = True, owner = False ) )
self.database.execute( self.user2.sql_save_notebook( self.notebook.object_id, read_write = True, owner = False, rank = 0 ) )
self.database.execute( self.user2.sql_save_notebook( self.notebook.trash_id, read_write = True, owner = False, rank = 0 ) )
def make_users( self ):
self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
@ -2299,6 +2299,11 @@ class Test_notebooks( Test_controller ):
assert notebook.owner == True
assert notebook.trash_id
self.user.sql_load_notebooks()
notebooks = self.database.select_many( Notebook, self.user.sql_load_notebooks() )
new_notebook = [ notebook for notebook in notebooks if notebook.object_id == new_notebook_id ][ 0 ]
assert new_notebook.rank == 1
def test_contents_after_create( self ):
self.login()
@ -2423,8 +2428,8 @@ class Test_notebooks( Test_controller ):
self.database.save( trash, commit = False )
notebook = Notebook.create( self.database.next_id( Notebook ), u"notebook", trash.object_id )
self.database.save( notebook, commit = False )
self.database.execute( self.user.sql_save_notebook( notebook.object_id, read_write = True, owner = True ) )
self.database.execute( self.user.sql_save_notebook( notebook.trash_id, read_write = True, owner = True ) )
self.database.execute( self.user.sql_save_notebook( notebook.object_id, read_write = True, owner = True, rank = 1 ) )
self.database.execute( self.user.sql_save_notebook( notebook.trash_id, read_write = True, owner = True, rank = 1 ) )
self.database.commit()
self.login()
@ -2447,8 +2452,8 @@ class Test_notebooks( Test_controller ):
self.database.save( trash, commit = False )
notebook = Notebook.create( self.database.next_id( Notebook ), u"notebook", trash.object_id )
self.database.save( notebook, commit = False )
self.database.execute( self.user.sql_save_notebook( notebook.object_id, read_write = False, owner = False ) )
self.database.execute( self.user.sql_save_notebook( notebook.trash_id, read_write = False, owner = False ) )
self.database.execute( self.user.sql_save_notebook( notebook.object_id, read_write = False, owner = False, rank = 1 ) )
self.database.execute( self.user.sql_save_notebook( notebook.trash_id, read_write = False, owner = False, rank = 1 ) )
self.database.commit()
self.login()

View File

@ -68,9 +68,9 @@ class Test_users( Test_controller ):
self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
self.database.save( self.user, commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id1, read_write = True, owner = True ), commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id1, read_write = True, owner = True, rank = 0 ), commit = False )
self.database.execute( self.user.sql_save_notebook( trash_id1, read_write = True, owner = True ), commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id2, read_write = True, owner = True ), commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id2, read_write = True, owner = True, rank = 1 ), commit = False )
self.database.execute( self.user.sql_save_notebook( trash_id2, read_write = True, owner = True ), commit = False )
self.user2 = User.create( self.database.next_id( User ), self.username2, self.password2, self.email_address2 )
@ -143,6 +143,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id
assert notebook.read_write == True
assert notebook.owner == True
assert notebook.rank == 0
notebook = notebooks[ 1 ]
assert notebook.object_id == notebooks[ 0 ].trash_id
@ -151,6 +152,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id == None
assert notebook.read_write == True
assert notebook.owner == True
assert notebook.rank == None
notebook = notebooks[ 2 ]
assert notebook.object_id == self.anon_notebook.object_id
@ -159,6 +161,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id == None
assert notebook.read_write == False
assert notebook.owner == False
assert notebook.rank == None
assert result.get( u"login_url" ) is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -222,6 +225,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id
assert notebook.read_write == False
assert notebook.owner == False
assert notebook.rank == 1
notebook = notebooks.get( self.notebooks[ 0 ].trash_id )
assert notebook.revision
@ -229,6 +233,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id == None
assert notebook.read_write == False
assert notebook.owner == False
assert notebook.rank == None
notebook = notebooks.get( self.anon_notebook.object_id )
assert notebook.revision == self.anon_notebook.revision
@ -236,6 +241,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id == None
assert notebook.read_write == False
assert notebook.owner == False
assert notebook.rank == None
assert result.get( u"login_url" ) is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -283,6 +289,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id
assert notebook.read_write == True
assert notebook.owner == True
assert notebook.rank == 0
notebook = notebooks[ 1 ]
assert notebook.object_id == notebooks[ 0 ].trash_id
@ -291,6 +298,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id == None
assert notebook.read_write == True
assert notebook.owner == True
assert notebook.rank == None
notebook = notebooks[ 2 ]
assert notebook.object_id == self.anon_notebook.object_id
@ -299,6 +307,7 @@ class Test_users( Test_controller ):
assert notebook.trash_id == None
assert notebook.read_write == False
assert notebook.owner == False
assert notebook.rank == None
assert result.get( u"login_url" ) is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -379,22 +388,27 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name
assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == 0
assert result[ u"notebooks" ][ 1 ].object_id
assert result[ u"notebooks" ][ 1 ].name == u"trash"
assert result[ u"notebooks" ][ 1 ].read_write == True
assert result[ u"notebooks" ][ 1 ].owner == True
assert result[ u"notebooks" ][ 1 ].rank == None
assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 1 ].object_id
assert result[ u"notebooks" ][ 2 ].name == self.notebooks[ 1 ].name
assert result[ u"notebooks" ][ 2 ].read_write == True
assert result[ u"notebooks" ][ 2 ].owner == True
assert result[ u"notebooks" ][ 2 ].rank == 1
assert result[ u"notebooks" ][ 3 ].object_id
assert result[ u"notebooks" ][ 3 ].name == u"trash"
assert result[ u"notebooks" ][ 3 ].read_write == True
assert result[ u"notebooks" ][ 3 ].owner == True
assert result[ u"notebooks" ][ 3 ].rank == None
assert result[ u"notebooks" ][ 4 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 4 ].name == self.anon_notebook.name
assert result[ u"notebooks" ][ 4 ].read_write == False
assert result[ u"notebooks" ][ 4 ].owner == False
assert result[ u"notebooks" ][ 4 ].rank == None
assert result[ u"login_url" ] is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -412,6 +426,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
assert result[ u"notebooks" ][ 0 ].read_write == False
assert result[ u"notebooks" ][ 0 ].owner == False
assert result[ u"notebooks" ][ 0 ].rank == None
login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) )
assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % (
@ -426,7 +441,7 @@ class Test_users( Test_controller ):
assert rate_plan[ u"name" ] == u"super"
assert rate_plan[ u"storage_quota_bytes" ] == 1337 * 10
def test_current_after_login_with_invite_id( self ):
def test_login_with_invite_id( self ):
# trick send_invites() into using a fake SMTP server
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
@ -461,7 +476,7 @@ class Test_users( Test_controller ):
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
def test_current_after_login_with_after_login( self ):
def test_login_with_after_login( self ):
after_login = u"/foo/bar"
result = self.http_post( "/users/login", dict(
@ -473,7 +488,7 @@ class Test_users( Test_controller ):
assert result[ u"redirect" ] == after_login
def test_current_after_login_with_after_login_with_full_url( self ):
def test_login_with_after_login_with_full_url( self ):
after_login = u"http://this_url/does/not/start/with/a/slash"
result = self.http_post( "/users/login", dict(
@ -601,6 +616,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
assert result[ u"notebooks" ][ 0 ].read_write == False
assert result[ u"notebooks" ][ 0 ].owner == False
assert result[ u"notebooks" ][ 0 ].rank == None
login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) )
assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % (
@ -2104,6 +2120,9 @@ class Test_users( Test_controller ):
assert result[ u"error" ]
def test_convert_invite_to_access( self ):
# start the invitee out with access to one notebook
self.database.execute( self.user2.sql_save_notebook( self.notebooks[ 1 ].object_id, read_write = True, owner = False, rank = 7 ), commit = False )
# trick send_invites() into using a fake SMTP server
Stub_smtp.reset()
smtplib.SMTP = Stub_smtp
@ -2144,6 +2163,12 @@ class Test_users( Test_controller ):
) )
assert access is True
self.user.sql_load_notebooks()
notebooks = self.database.select_many( Notebook, self.user2.sql_load_notebooks() )
new_notebook = [ notebook for notebook in notebooks if notebook.object_id == invite.notebook_id ][ 0 ]
print new_notebook.rank
assert new_notebook.rank == 8 # one higher than the other notebook this user has access to
assert invite.redeemed_user_id == self.user2.object_id
def test_convert_invite_to_access_same_user( self ):
@ -3245,6 +3270,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name
assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == 0
assert result[ u"login_url" ] == None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -3284,6 +3310,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name
assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == 0
assert result[ u"login_url" ] == None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -3322,6 +3349,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name
assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == 0
assert result[ u"login_url" ] == None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -3360,6 +3388,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name
assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == 0
assert result[ u"login_url" ] == None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"
@ -3396,6 +3425,7 @@ class Test_users( Test_controller ):
assert result[ u"notebooks" ][ 0 ].name == self.notebooks[ 0 ].name
assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == 0
assert result[ u"login_url" ] == None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout"

View File

@ -12,7 +12,8 @@ class Notebook( Persistent ):
WHITESPACE_PATTERN = re.compile( r"\s+" )
SEARCH_OPERATORS = re.compile( r"[&|!()'\\:]" )
def __init__( self, object_id, revision = None, name = None, trash_id = None, deleted = False, user_id = None, read_write = True, owner = True ):
def __init__( self, object_id, revision = None, name = None, trash_id = None, deleted = False,
user_id = None, read_write = True, owner = True, rank = None ):
"""
Create a new notebook with the given id and name.
@ -32,6 +33,8 @@ class Notebook( Persistent ):
@param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
@type owner: bool or NoneType
@param owner: whether this view of the notebook currently has owner-level access (optional, defaults to True)
@type rank: float or NoneType
@param rank: indicates numeric ordering of this note in relation to other notebooks
@rtype: Notebook
@return: newly constructed notebook
"""
@ -42,9 +45,10 @@ class Notebook( Persistent ):
self.__user_id = user_id
self.__read_write = read_write
self.__owner = owner
self.__rank = rank
@staticmethod
def create( object_id, name = None, trash_id = None, deleted = False, user_id = None, read_write = True, owner = True ):
def create( object_id, name = None, trash_id = None, deleted = False, user_id = None, read_write = True, owner = True, rank = None ):
"""
Convenience constructor for creating a new notebook.
@ -62,10 +66,12 @@ class Notebook( Persistent ):
@param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
@type owner: bool or NoneType
@param owner: whether this view of the notebook currently has owner-level access (optional, defaults to True)
@type rank: float or NoneType
@param rank: indicates numeric ordering of this note in relation to other notebooks
@rtype: Notebook
@return: newly constructed notebook
"""
return Notebook( object_id, name = name, trash_id = trash_id, user_id = user_id, read_write = read_write, owner = owner )
return Notebook( object_id, name = name, trash_id = trash_id, user_id = user_id, read_write = read_write, owner = owner, rank = rank )
@staticmethod
def sql_load( object_id, revision = None ):
@ -182,7 +188,7 @@ class Notebook( Persistent ):
) as sub;
""" % ( quote( search_text ), quote( self.object_id ) )
def sql_highest_rank( self ):
def sql_highest_note_rank( self ):
"""
Return a SQL string to determine the highest numbered rank of all notes in this notebook."
"""
@ -232,9 +238,15 @@ class Notebook( Persistent ):
self.__user_id = user_id
self.update_revision()
def __set_rank( self, rank ):
# The rank member isn't actually saved to the database, so setting it doesn't need to
# call update_revision().
self.__rank = rank
name = property( lambda self: self.__name, __set_name )
trash_id = property( lambda self: self.__trash_id )
read_write = property( lambda self: self.__read_write, __set_read_write )
owner = property( lambda self: self.__owner, __set_owner )
deleted = property( lambda self: self.__deleted, __set_deleted )
user_id = property( lambda self: self.__user_id, __set_user_id )
rank = property( lambda self: self.__rank, __set_rank )

View File

@ -149,18 +149,27 @@ class User( Persistent ):
read_write_clause = ""
return \
"select notebook_current.*, user_notebook.read_write, user_notebook.owner from user_notebook, notebook_current " + \
"where user_notebook.user_id = %s%s%s%s and user_notebook.notebook_id = notebook_current.id order by revision;" % \
( quote( self.object_id ), parents_only_clause, undeleted_only_clause, read_write_clause )
"""
select
notebook_current.*, user_notebook.read_write, user_notebook.owner, user_notebook.rank
from
user_notebook, notebook_current
where
user_notebook.user_id = %s%s%s%s and
user_notebook.notebook_id = notebook_current.id
order by user_notebook.rank;
""" % ( quote( self.object_id ), parents_only_clause, undeleted_only_clause, read_write_clause )
def sql_save_notebook( self, notebook_id, read_write = True, owner = True ):
def sql_save_notebook( self, notebook_id, read_write = True, owner = True, rank = None ):
"""
Return a SQL string to save the id of a notebook to which this user has access.
"""
if rank is None: rank = quote( None )
return \
"insert into user_notebook ( user_id, notebook_id, read_write, owner ) values " + \
"insert into user_notebook ( user_id, notebook_id, read_write, owner, rank ) values " + \
"( %s, %s, %s, %s );" % ( quote( self.object_id ), quote( notebook_id ), quote( read_write and 't' or 'f' ),
quote( owner and 't' or 'f' ) )
quote( owner and 't' or 'f' ), rank )
def sql_remove_notebook( self, notebook_id ):
"""
@ -199,6 +208,20 @@ class User( Persistent ):
( quote( read_write and 't' or 'f' ), quote( owner and 't' or 'f' ), quote( self.object_id ),
quote( notebook_id ) )
def sql_update_notebook_rank( self, notebook_id, rank ):
"""
Return a SQL string to update the user's rank for the given notebook.
"""
return \
"update user_notebook set rank = %s where user_id = %s and notebook_id = %s;" % \
( quote( rank ), quote( self.object_id ), quote( notebook_id ) )
def sql_highest_notebook_rank( self ):
"""
Return a SQL string to determine the highest numbered rank of all notebooks the user has access to."
"""
return "select coalesce( max( rank ), -1 ) from user_notebook where user_id = %s;" % quote( self.object_id )
@staticmethod
def sql_revoke_invite_access( notebook_id, trash_id, email_address ):
return \

1
model/delta/1.2.14.sql Normal file
View File

@ -0,0 +1 @@
alter table user_notebook add column rank numeric;

View File

@ -165,7 +165,8 @@ CREATE TABLE user_notebook (
user_id text NOT NULL,
notebook_id text NOT NULL,
read_write boolean DEFAULT false,
"owner" boolean DEFAULT false
"owner" boolean DEFAULT false,
rank numeric
);

View File

@ -12,9 +12,12 @@ class Test_notebook( object ):
self.trash_name = u"trash"
self.user_id = u"me"
self.delta = timedelta( seconds = 1 )
self.read_write = True
self.owner = False
self.rank = 17.5
self.trash = Notebook.create( self.trash_id, self.trash_name, read_write = False, deleted = False, user_id = self.user_id )
self.notebook = Notebook.create( self.object_id, self.name, trash_id = self.trash.object_id, deleted = False, user_id = self.user_id )
self.notebook = Notebook.create( self.object_id, self.name, trash_id = self.trash.object_id, deleted = False, user_id = self.user_id, read_write = self.read_write, owner = self.owner, rank = self.rank )
self.note = Note.create( "19", u"<h3>title</h3>blah" )
def test_create( self ):
@ -25,6 +28,9 @@ class Test_notebook( object ):
assert self.notebook.trash_id == self.trash_id
assert self.notebook.deleted == False
assert self.notebook.user_id == self.user_id
assert self.notebook.read_write == self.read_write
assert self.notebook.owner == self.owner
assert self.notebook.rank == self.rank
assert self.trash.object_id == self.trash_id
assert datetime.now( tz = utc ) - self.trash.revision < self.delta
@ -33,6 +39,9 @@ class Test_notebook( object ):
assert self.trash.trash_id == None
assert self.trash.deleted == False
assert self.trash.user_id == self.user_id
assert self.trash.read_write == False
assert self.trash.owner == True
assert self.trash.rank == None
def test_set_name( self ):
new_name = u"my new notebook"
@ -63,6 +72,13 @@ class Test_notebook( object ):
assert self.notebook.user_id == u"5"
assert self.notebook.revision > previous_revision
def test_set_rank( self ):
original_revision = self.notebook.revision
self.notebook.rank = 17.7
assert self.notebook.rank == 17.7
assert self.notebook.revision == original_revision
def test_to_dict( self ):
d = self.notebook.to_dict()

View File

@ -221,6 +221,18 @@ img {
background-image: url(/static/images/numbered_list_button_down.png);
}
#current_notebook_up_hover_preload {
height: 0;
overflow: hidden;
background-image: url(/static/images/arrow_up_hover.png);
}
#current_notebook_down_hover_preload {
height: 0;
overflow: hidden;
background-image: url(/static/images/arrow_down_hover.png);
}
#link_area {
float: right;
text-align: left;
@ -564,6 +576,16 @@ img {
background: url(/static/images/current_notebook_inner_tl.png) no-repeat top left;
}
#current_notebook_up {
cursor: pointer;
padding-top: 1px;
}
#current_notebook_down {
cursor: pointer;
padding-top: 1px;
}
.trash_notebook_color {
background-color: #d0d0d0;
}

View File

@ -1,3 +1,5 @@
IMAGE_DIR = "/static/images/";
function Wiki( invoker ) {
this.next_id = null;
this.focused_editor = null;
@ -105,9 +107,28 @@ function Wiki( invoker ) {
"href": "/notebooks/" + this.notebook.trash_id + "?parent_id=" + this.notebook.object_id
}, "trash" );
var message_div = this.display_message( "The notebook has been moved to the", [ trash_link, ". ", undo_button ], "notes_top" );
var self = this;
connect( undo_button, "onclick", function ( event ) { self.undelete_notebook( event, deleted_id ); } );
}
var current_notebook_up = getElement( "current_notebook_up" );
if ( current_notebook_up ) {
connect( current_notebook_up, "onmouseover", function ( event ) { current_notebook_up.src = IMAGE_DIR + "up_arrow_hover.png"; } );
connect( current_notebook_up, "onmouseout", function ( event ) { current_notebook_up.src = IMAGE_DIR + "up_arrow.png"; } );
connect( current_notebook_up, "onclick", function ( event ) {
current_notebook_up.src = IMAGE_DIR + "up_arrow.png";
self.move_current_notebook_up( event );
} );
}
var current_notebook_down = getElement( "current_notebook_down" );
if ( current_notebook_down ) {
connect( current_notebook_down, "onmouseover", function ( event ) { current_notebook_down.src = IMAGE_DIR + "down_arrow_hover.png"; } );
connect( current_notebook_down, "onmouseout", function ( event ) { current_notebook_down.src = IMAGE_DIR + "down_arrow.png"; } );
connect( current_notebook_down, "onclick", function ( event ) {
current_notebook_down.src = IMAGE_DIR + "down_arrow.png";
self.move_current_notebook_down( event );
} );
}
}
Wiki.prototype.update_next_id = function ( result ) {
@ -851,8 +872,6 @@ Wiki.prototype.editor_key_pressed = function ( editor, event ) {
}
}
IMAGE_DIR = "/static/images/";
Wiki.prototype.make_image_button = function ( name, filename_prefix, handle_mouse_up_and_down ) {
var button = getElement( name );
@ -1530,6 +1549,56 @@ Wiki.prototype.display_invites = function ( invite_area ) {
replaceChildNodes( invite_area, div );
}
Wiki.prototype.move_current_notebook_up = function ( event ) {
var current_notebook = getElement( "current_notebook_wrapper" );
var sibling_notebook = current_notebook;
// find the previous sibling notebook node
do {
var sibling_notebook = sibling_notebook.previousSibling;
} while ( sibling_notebook && sibling_notebook.className != "link_area_item" );
removeElement( current_notebook );
if ( sibling_notebook )
// move the current notebook up before the previous notebook node
insertSiblingNodesBefore( sibling_notebook, current_notebook );
// if the current notebook is the first one, wrap it around to the bottom of the list
else {
var notebooks_area = getElement( "notebooks_area" );
appendChildNodes( notebooks_area, current_notebook );
}
var self = this;
this.invoker.invoke( "/notebooks/move_up", "POST", {
"notebook_id": this.notebook_id
} );
}
Wiki.prototype.move_current_notebook_down = function ( event ) {
var current_notebook = getElement( "current_notebook_wrapper" );
var sibling_notebook = current_notebook;
// find the next sibling notebook node
do {
var sibling_notebook = sibling_notebook.nextSibling;
} while ( sibling_notebook && sibling_notebook.className != "link_area_item" );
removeElement( current_notebook );
if ( sibling_notebook )
// move the current notebook down after the previous notebook node
insertSiblingNodesAfter( sibling_notebook, current_notebook );
// if the current notebook is the last one, wrap it around to the top of the list
else {
var notebooks_area_title = getElement( "notebooks_area_title" );
insertSiblingNodesAfter( notebooks_area_title, current_notebook );
}
var self = this;
this.invoker.invoke( "/notebooks/move_down", "POST", {
"notebook_id": this.notebook_id
} );
}
Wiki.prototype.display_message = function ( text, nodes, position_after ) {
this.clear_messages();
this.clear_pulldowns();

View File

@ -1,4 +1,4 @@
from Tags import Div, Span, H4, A, Strong
from Tags import Div, Span, H4, A, Strong, Img
from Rounded_div import Rounded_div
@ -115,7 +115,7 @@ class Link_area( Div ):
),
Div(
( len( linked_notebooks ) > 0 ) and H4( u"notebooks" ) or None,
( len( linked_notebooks ) > 0 ) and H4( u"notebooks", id = u"notebooks_area_title" ) or None,
[ ( nb.object_id == notebook.object_id ) and Rounded_div(
u"current_notebook",
A(
@ -123,6 +123,12 @@ class Link_area( Div ):
href = u"/notebooks/%s" % nb.object_id,
id = u"notebook_%s" % nb.object_id,
),
( len( linked_notebooks ) > 1 ) and Span(
Img( src = u"/static/images/up_arrow.png", width = u"20", height = u"17", id = u"current_notebook_up" ),
Img( src = u"/static/images/down_arrow.png", width = u"20", height = u"17", id = u"current_notebook_down" ),
Span( id = "current_notebook_up_hover_preload" ),
Span( id = "current_notebook_down_hover_preload" ),
) or None,
class_ = u"link_area_item",
) or
Div(

View File

@ -22,5 +22,6 @@ class Rounded_div( Div ):
Div.__init__(
self,
div,
id = u"%s_wrapper" % image_name,
class_ = u"%s_color" % image_name,
)