From 5520fe58928a9e7a7f915c2b66d404852481af69 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 4 Jan 2008 04:45:43 +0000 Subject: [PATCH] * make a User_revision object containing a revision timestamp, user_id, username * change controller.Notebooks.load_note_revisions() to select and return User_revision objects * change controller.Notebooks.save_note() to use User_revision objects for new_revision and previous_revision as well * update client to deal with new load_note_revisions() return values (make sure all uses of revisions_list are updated) * update client to deal with new new_revision/previous_revision * update changes pulldown to show username along with each timestamp * update model.Invite to load redeemed_username along with redeemed_user_id * display the redeemed username next to each email address in the "share this notebook" note --- NEWS | 4 +- controller/Notebooks.py | 30 +++++----- controller/test/Test_controller.py | 14 ++++- controller/test/Test_notebooks.py | 91 ++++++++++++++++++++++-------- model/Invite.py | 55 +++++++++++++++--- model/Note.py | 11 +++- model/User_revision.py | 30 ++++++++++ model/delta/1.1.0.sql | 17 ++---- model/test/Test_invite.py | 2 + model/test/Test_user.py | 9 +++ model/test/Test_user_revision.py | 23 ++++++++ static/js/Editor.js | 2 +- static/js/Wiki.js | 55 +++++++++++------- 13 files changed, 263 insertions(+), 80 deletions(-) create mode 100644 model/User_revision.py create mode 100644 model/test/Test_user_revision.py diff --git a/NEWS b/NEWS index 67cc55e..5658225 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,7 @@ 1.1.0: January ??, 2007 - * + * Ability to invite people to your notebook as a collaborator or owner. + * Fixed bug where passwords with special characters broke password hashing. + * Fixed bug where link info box summaries sometimes contained HTML tags. 1.0.4: December 30, 2007 * Ability to invite people to view your notebook. diff --git a/controller/Notebooks.py b/controller/Notebooks.py index e82fbc3..f63cd30 100644 --- a/controller/Notebooks.py +++ b/controller/Notebooks.py @@ -11,6 +11,7 @@ from model.Notebook import Notebook from model.Note import Note from model.Invite import Invite from model.User import User +from model.User_revision import User_revision from view.Main_page import Main_page from view.Json import Json from view.Html_file import Html_file @@ -374,7 +375,7 @@ class Notebooks( object ): @type user_id: unicode or NoneType @param user_id: id of current logged-in user (if any), determined by @grab_user_id @rtype: json dict - @return: { 'revisions': revisionslist or None } + @return: { 'revisions': userrevisionlist or None } @raise Access_error: the current user doesn't have access to the given notebook or note @raise Validation_error: one of the arguments is invalid """ @@ -398,7 +399,7 @@ class Notebooks( object ): raise Access_error() - revisions = self.__database.select_many( unicode, note.sql_load_revisions() ) + revisions = self.__database.select_many( User_revision, note.sql_load_revisions() ) else: revisions = None @@ -438,8 +439,8 @@ class Notebooks( object ): @param user_id: id of current logged-in user (if any), determined by @grab_user_id @rtype: json dict @return: { - 'new_revision': new revision of saved note, or None if nothing was saved, - 'previous_revision': revision immediately before new_revision, or None if the note is new + 'new_revision': User_revision of saved note, or None if nothing was saved + 'previous_revision': User_revision immediately before new_revision, or None if the note is new 'storage_bytes': current storage usage by user, } @raise Access_error: the current user doesn't have access to the given notebook @@ -448,15 +449,16 @@ class Notebooks( object ): if not self.__users.check_access( user_id, notebook_id, read_write = True ): raise Access_error() + user = self.__database.load( User, user_id ) notebook = self.__database.load( Notebook, notebook_id ) - if not notebook: + if not user or not notebook: raise Access_error() note = self.__database.load( Note, note_id ) # check whether the provided note contents have been changed since the previous revision - def update_note( current_notebook, old_note, startup, user_id ): + def update_note( current_notebook, old_note, startup, user ): # the note hasn't been changed, so bail without updating it if contents.replace( u"\n", u"" ) == old_note.contents.replace( u"\n", "" ) and startup == old_note.startup: new_revision = None @@ -469,9 +471,9 @@ class Notebooks( object ): note.rank = self.__database.select_one( float, notebook.sql_highest_rank() ) + 1 else: note.rank = None - note.user_id = user_id + note.user_id = user.object_id - new_revision = note.revision + new_revision = User_revision( note.revision, note.user_id, user.username ) return new_revision @@ -479,19 +481,21 @@ class Notebooks( object ): if note and note.notebook_id == notebook.object_id: old_note = self.__database.load( Note, note_id, previous_revision ) - previous_revision = note.revision - new_revision = update_note( notebook, old_note, startup, user_id ) + previous_user = self.__database.load( User, note.user_id ) + previous_revision = User_revision( note.revision, note.user_id, previous_user and previous_user.username or None ) + new_revision = update_note( notebook, old_note, startup, user ) # the note is not already in the given notebook, so look for it in the trash elif note and notebook.trash_id and note.notebook_id == notebook.trash_id: old_note = self.__database.load( Note, note_id, previous_revision ) # undelete the note, putting it back in the given notebook - previous_revision = note.revision + previous_user = self.__database.load( User, note.user_id ) + previous_revision = User_revision( note.revision, note.user_id, previous_user and previous_user.username or None ) note.notebook_id = notebook.object_id note.deleted_from_id = None - new_revision = update_note( notebook, old_note, startup, user_id ) + new_revision = update_note( notebook, old_note, startup, user ) # otherwise, create a new note else: if startup: @@ -501,7 +505,7 @@ class Notebooks( object ): previous_revision = None note = Note.create( note_id, contents, notebook_id = notebook.object_id, startup = startup, rank = rank, user_id = user_id ) - new_revision = note.revision + new_revision = User_revision( note.revision, note.user_id, user.username ) if new_revision: self.__database.save( note, commit = False ) diff --git a/controller/test/Test_controller.py b/controller/test/Test_controller.py index 24d4898..9e5ccd2 100644 --- a/controller/test/Test_controller.py +++ b/controller/test/Test_controller.py @@ -13,6 +13,7 @@ class Test_controller( object ): from model.Notebook import Notebook from model.Note import Note from model.Invite import Invite + from model.User_revision import User_revision # Since Stub_database isn't a real database and doesn't know SQL, replace some of the # SQL-returning methods in User, Note, and Notebook to return functions that manipulate data in @@ -141,8 +142,19 @@ class Test_controller( object ): def sql_load_revisions( self, database ): note_list = database.objects.get( self.object_id ) if not note_list: return None + revisions = [] + + for note in note_list: + user_list = database.objects.get( note.user_id ) + user_id = None + username = None + + if user_list: + user_id = user_list[ -1 ].object_id + username = user_list[ -1 ].username + + revisions.append( User_revision( note.revision, user_id, username ) ) - revisions = [ note.revision for note in note_list ] return revisions Note.sql_load_revisions = lambda self: \ diff --git a/controller/test/Test_notebooks.py b/controller/test/Test_notebooks.py index 6d1e5f5..af023ba 100644 --- a/controller/test/Test_notebooks.py +++ b/controller/test/Test_notebooks.py @@ -686,7 +686,9 @@ class Test_notebooks( Test_controller ): revisions = result[ "revisions" ] assert revisions != None assert len( revisions ) == 1 - assert revisions[ 0 ] == self.note.revision + assert revisions[ 0 ].revision == self.note.revision + assert revisions[ 0 ].user_id == self.user.object_id + assert revisions[ 0 ].username == self.username def test_save_note( self, startup = False ): self.login() @@ -702,9 +704,14 @@ class Test_notebooks( Test_controller ): previous_revision = previous_revision, ), session_id = self.session_id ) - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision - current_revision = result[ "new_revision" ] - assert result[ "previous_revision" ] == previous_revision + assert result[ "new_revision" ] + assert result[ "new_revision" ].revision != previous_revision + assert result[ "new_revision" ].user_id == self.user.object_id + assert result[ "new_revision" ].username == self.username + current_revision = result[ "new_revision" ].revision + assert result[ "previous_revision" ].revision == previous_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes > 0 assert result[ "storage_bytes" ] == user.storage_bytes @@ -746,8 +753,12 @@ class Test_notebooks( Test_controller ): revisions = result[ "revisions" ] assert revisions != None assert len( revisions ) == 2 - assert revisions[ 0 ] == previous_revision - assert revisions[ 1 ] == current_revision + assert revisions[ 0 ].revision == previous_revision + assert revisions[ 0 ].user_id == self.user.object_id + assert revisions[ 0 ].username == self.username + assert revisions[ 1 ].revision == current_revision + assert revisions[ 1 ].user_id == self.user.object_id + assert revisions[ 1 ].username == self.username def test_save_startup_note( self ): self.test_save_note( startup = True ) @@ -790,8 +801,13 @@ class Test_notebooks( Test_controller ): previous_revision = previous_revision, ), session_id = self.session_id ) - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision - assert result[ "previous_revision" ] == previous_revision + assert result[ "new_revision" ] + assert result[ "new_revision" ].revision != previous_revision + assert result[ "new_revision" ].user_id == self.user.object_id + assert result[ "new_revision" ].username == self.username + assert result[ "previous_revision" ].revision == previous_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes > 0 assert result[ "storage_bytes" ] == user.storage_bytes @@ -851,7 +867,7 @@ class Test_notebooks( Test_controller ): # now attempt to save over that note again without changing the contents user = self.database.load( User, self.user.object_id ) previous_storage_bytes = user.storage_bytes - previous_revision = result[ "new_revision" ] + previous_revision = result[ "new_revision" ].revision result = self.http_post( "/notebooks/save_note/", dict( notebook_id = self.notebook.object_id, note_id = self.note.object_id, @@ -862,7 +878,9 @@ class Test_notebooks( Test_controller ): # assert that the note wasn't actually updated the second time assert result[ "new_revision" ] == None - assert result[ "previous_revision" ] == previous_revision + assert result[ "previous_revision" ].revision == previous_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == previous_storage_bytes assert result[ "storage_bytes" ] == 0 @@ -902,7 +920,7 @@ class Test_notebooks( Test_controller ): # now attempt to save over that note again without changing the contents user = self.database.load( User, self.user.object_id ) previous_storage_bytes = user.storage_bytes - previous_revision = result[ "new_revision" ] + previous_revision = result[ "new_revision" ].revision result = self.http_post( "/notebooks/save_note/", dict( notebook_id = self.notebook.object_id, note_id = self.note.object_id, @@ -913,7 +931,9 @@ class Test_notebooks( Test_controller ): # assert that the note wasn't actually updated the second time assert result[ "new_revision" ] == None - assert result[ "previous_revision" ] == previous_revision + assert result[ "previous_revision" ].revision == previous_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == previous_storage_bytes assert result[ "storage_bytes" ] == 0 @@ -956,7 +976,7 @@ class Test_notebooks( Test_controller ): # now attempt to save over that note again without changing the contents, but with a change # to its startup flag - previous_revision = result[ "new_revision" ] + previous_revision = result[ "new_revision" ].revision result = self.http_post( "/notebooks/save_note/", dict( notebook_id = self.notebook.object_id, note_id = self.note.object_id, @@ -966,8 +986,13 @@ class Test_notebooks( Test_controller ): ), session_id = self.session_id ) # assert that the note was updated the second time - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision - assert result[ "previous_revision" ] == previous_revision + assert result[ "new_revision" ] + assert result[ "new_revision" ].revision != previous_revision + assert result[ "new_revision" ].user_id == self.user.object_id + assert result[ "new_revision" ].username == self.username + assert result[ "previous_revision" ].revision == previous_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes > 0 assert result[ "storage_bytes" ] == user.storage_bytes @@ -1009,7 +1034,7 @@ class Test_notebooks( Test_controller ): # except for adding a newline user = self.database.load( User, self.user.object_id ) previous_storage_bytes = user.storage_bytes - previous_revision = result[ "new_revision" ] + previous_revision = result[ "new_revision" ].revision result = self.http_post( "/notebooks/save_note/", dict( notebook_id = self.notebook.object_id, note_id = self.note.object_id, @@ -1020,7 +1045,9 @@ class Test_notebooks( Test_controller ): # assert that the note wasn't actually updated the second time assert result[ "new_revision" ] == None - assert result[ "previous_revision" ] == previous_revision + assert result[ "previous_revision" ].revision == previous_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == previous_storage_bytes assert result[ "storage_bytes" ] == 0 @@ -1054,7 +1081,7 @@ class Test_notebooks( Test_controller ): # save over that note again with new contents, providing the original # revision as the previous known revision - second_revision = result[ "new_revision" ] + second_revision = result[ "new_revision" ].revision new_note_contents = u"

new new title

new new blah" result = self.http_post( "/notebooks/save_note/", dict( notebook_id = self.notebook.object_id, @@ -1066,8 +1093,12 @@ class Test_notebooks( Test_controller ): # make sure the second save actually caused an update assert result[ "new_revision" ] - assert result[ "new_revision" ] not in ( first_revision, second_revision ) - assert result[ "previous_revision" ] == second_revision + assert result[ "new_revision" ].revision not in ( first_revision, second_revision ) + assert result[ "new_revision" ].user_id == self.user.object_id + assert result[ "new_revision" ].username == self.username + assert result[ "previous_revision" ].revision == second_revision + assert result[ "previous_revision" ].user_id == self.user.object_id + assert result[ "previous_revision" ].username == self.username user = self.database.load( User, self.user.object_id ) assert user.storage_bytes > 0 assert result[ "storage_bytes" ] == user.storage_bytes @@ -1133,7 +1164,10 @@ class Test_notebooks( Test_controller ): previous_revision = None, ), session_id = self.session_id ) - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision + 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 @@ -1179,7 +1213,10 @@ class Test_notebooks( Test_controller ): previous_revision = None, ), session_id = self.session_id ) - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision + 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 @@ -1216,7 +1253,10 @@ class Test_notebooks( Test_controller ): previous_revision = None, ), session_id = self.session_id ) - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision + 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 @@ -1263,7 +1303,10 @@ class Test_notebooks( Test_controller ): previous_revision = None, ), session_id = self.session_id ) - assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision + 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 diff --git a/model/Invite.py b/model/Invite.py index a8d21c1..e1aa24a 100644 --- a/model/Invite.py +++ b/model/Invite.py @@ -6,7 +6,8 @@ class Invite( Persistent ): An invitiation to view or edit a notebook. """ def __init__( self, object_id, revision = None, from_user_id = None, notebook_id = None, - email_address = None, read_write = False, owner = False, redeemed_user_id = None ): + email_address = None, read_write = False, owner = False, redeemed_user_id = None, + redeemed_username = None ): """ Create an invitation with the given id. @@ -26,6 +27,8 @@ class Invite( Persistent ): @param owner: whether the invitation is for owner-level access (optional, defaults to False) @type redeemed_user_id: unicode or NoneType @param redeemed_user_id: id of the user who has redeemed this invitation, if any (optional) + @type redeemed_username: unicode or NoneType + @param redeemed_username: username of the user who has redeemed this invitation, if any (optional) @rtype: Invite @return: newly constructed notebook invitation """ @@ -36,6 +39,7 @@ class Invite( Persistent ): self.__read_write = read_write self.__owner = owner self.__redeemed_user_id = redeemed_user_id + self.__redeemed_username = redeemed_username @staticmethod def create( object_id, from_user_id = None, notebook_id = None, email_address = None, read_write = False, owner = False ): @@ -66,7 +70,18 @@ class Invite( Persistent ): if revision: raise NotImplementedError() - return "select id, revision, from_user_id, notebook_id, email_address, read_write, owner, redeemed_user_id from invite where id = %s;" % quote( object_id ) + return \ + """ + select + invite.id, invite.revision, invite.from_user_id, invite.notebook_id, invite.email_address, + invite.read_write, invite.owner, invite.redeemed_user_id, luminotes_user_current.username + from + invite left outer join luminotes_user_current + on + ( invite.redeemed_user_id = luminotes_user_current.id ) + where + invite.id = %s; + """ % quote( object_id ) @staticmethod def sql_id_exists( object_id, revision = None ): @@ -92,16 +107,36 @@ class Invite( Persistent ): def sql_load_similar( self ): # select invites with the same notebook_id, and email_address as this invite - return "select id, revision, from_user_id, notebook_id, email_address, read_write, owner, redeemed_user_id from invite " + \ - "where notebook_id = %s and email_address = %s and id != %s;" % \ - ( quote( self.__notebook_id ), quote( self.__email_address ), quote( self.object_id ) ) + return \ + """ + select + invite.id, invite.revision, invite.from_user_id, invite.notebook_id, invite.email_address, + invite.read_write, invite.owner, invite.redeemed_user_id, luminotes_user_current.username + from + invite left outer join luminotes_user_current + on + ( invite.redeemed_user_id = luminotes_user_current.id ) + where + invite.notebook_id = %s and invite.email_address = %s and invite.id != %s; + """ % ( quote( self.__notebook_id ), quote( self.__email_address ), quote( self.object_id ) ) @staticmethod def sql_load_notebook_invites( notebook_id ): - # select a list of invites to the given notebook. if there are multiple invites for a given - # email_address, arbitrarily pick one of them - return "select id, revision, from_user_id, notebook_id, email_address, read_write, owner, redeemed_user_id from invite " + \ - "where id in ( select max( id ) from invite where notebook_id = %s group by email_address ) order by email_address;" % quote( notebook_id ) + # select a list of invites to the given notebook + return \ + """ + select + invite.id, invite.revision, invite.from_user_id, invite.notebook_id, invite.email_address, + invite.read_write, invite.owner, invite.redeemed_user_id, luminotes_user_current.username + from + invite left outer join luminotes_user_current + on + ( invite.redeemed_user_id = luminotes_user_current.id ) + where + invite.notebook_id = %s + order by + invite.email_address, invite.redeemed_user_id; + """ % quote( notebook_id ) def sql_revoke_invites( self ): return "delete from invite where notebook_id = %s and email_address = %s;" % \ @@ -116,6 +151,7 @@ class Invite( Persistent ): read_write = self.__read_write, owner = self.__owner, redeemed_user_id = self.__redeemed_user_id, + redeemed_username = self.__redeemed_username, ) ) return d @@ -141,3 +177,4 @@ class Invite( Persistent ): read_write = property( lambda self: self.__read_write, __set_read_write ) owner = property( lambda self: self.__owner, __set_owner ) redeemed_user_id = property( lambda self: self.__redeemed_user_id, __set_redeemed_user_id ) + redeemed_username = property( lambda self: self.__redeemed_username ) diff --git a/model/Note.py b/model/Note.py index 314d2c4..ff6bd9f 100644 --- a/model/Note.py +++ b/model/Note.py @@ -152,7 +152,16 @@ class Note( Persistent ): return self.sql_create() def sql_load_revisions( self ): - return "select revision from note where id = %s order by revision;" % quote( self.object_id ) + return """ \ + select + note.revision, luminotes_user_current.id, username + from + note left outer join luminotes_user_current + on + ( note.user_id = luminotes_user_current.id ) + where + note.id = %s order by note.revision; + """ % quote( self.object_id ) def to_dict( self ): d = Persistent.to_dict( self ) diff --git a/model/User_revision.py b/model/User_revision.py new file mode 100644 index 0000000..6eae8fa --- /dev/null +++ b/model/User_revision.py @@ -0,0 +1,30 @@ +class User_revision( object ): + """ + A revision timestamp along with information on the user that made that revision. + """ + def __init__( self, revision, user_id = None, username = None ): + """ + Create a User_revision with the given timestamp and user information. + + @type revision: datetime + @param revision: revision timestamp + @type user_id: unicode or NoneType + @param user_id: id of user who made this revision (optional, defaults to None) + @type username: username of user who made this revision (optional, defaults to None) + @rtype: User_revision + @return: newly constructed User_revision object + """ + self.__revision = revision + self.__user_id = user_id + self.__username = username + + def to_dict( self ): + return dict( + revision = self.__revision, + user_id = self.__user_id, + username = self.__username, + ) + + revision = property( lambda self: self.__revision ) + user_id = property( lambda self: self.__user_id ) + username = property( lambda self: self.__username ) diff --git a/model/delta/1.1.0.sql b/model/delta/1.1.0.sql index e50601a..a90a60d 100644 --- a/model/delta/1.1.0.sql +++ b/model/delta/1.1.0.sql @@ -1,11 +1,6 @@ -update notebook set user_id = ( - select user_notebook.user_id - from user_notebook - where notebook_id = notebook.id and read_write = 't' and owner = 't' - limit 1 -); -update note set user_id = ( - select notebook_current.user_id - from notebook_current - where note.notebook_id = notebook_current.id -); +update notebook set user_id = user_notebook.user_id + from user_notebook + where user_notebook.notebook_id = notebook.id and read_write = 't' and owner = 't'; +update note set user_id = notebook_current.user_id + from notebook_current + where note.notebook_id = notebook_current.id; diff --git a/model/test/Test_invite.py b/model/test/Test_invite.py index 0847c73..8dbe2a7 100644 --- a/model/test/Test_invite.py +++ b/model/test/Test_invite.py @@ -25,6 +25,7 @@ class Test_invite( object ): assert self.invite.read_write == self.read_write assert self.invite.owner == self.owner assert self.invite.redeemed_user_id == None + assert self.invite.redeemed_username == None def test_redeem( self ): previous_revision = self.invite.revision @@ -53,3 +54,4 @@ class Test_invite( object ): assert d.get( "read_write" ) == self.read_write assert d.get( "owner" ) == self.owner assert d.get( "redeemed_user_id" ) == None + assert d.get( "redeemed_username" ) == None diff --git a/model/test/Test_user.py b/model/test/Test_user.py index f454810..c2b0f58 100644 --- a/model/test/Test_user.py +++ b/model/test/Test_user.py @@ -36,6 +36,15 @@ class Test_user( object ): assert self.user.check_password( new_password ) == True assert self.user.revision > previous_revision + def test_set_password_with_special_characters( self ): + previous_revision = self.user.revision + new_password = u"newpass\xe4" + self.user.password = new_password + + assert self.user.check_password( self.password ) == False + assert self.user.check_password( new_password ) == True + assert self.user.revision > previous_revision + def test_set_none_password( self ): previous_revision = self.user.revision new_password = None diff --git a/model/test/Test_user_revision.py b/model/test/Test_user_revision.py new file mode 100644 index 0000000..5634ce6 --- /dev/null +++ b/model/test/Test_user_revision.py @@ -0,0 +1,23 @@ +from datetime import datetime +from model.User_revision import User_revision + + +class Test_user_revision( object ): + def setUp( self ): + self.revision = datetime.now() + self.user_id = u"77" + self.username = u"bob" + + self.user_revision = User_revision( self.revision, self.user_id, self.username ) + + def test_create( self ): + assert self.user_revision.revision == self.revision + assert self.user_revision.user_id == self.user_id + assert self.user_revision.username == self.username + + def test_to_dict( self ): + d = self.user_revision.to_dict() + + assert d.get( "revision" ) == self.revision + assert d.get( "user_id" ) == self.user_id + assert d.get( "username" ) == self.username diff --git a/static/js/Editor.js b/static/js/Editor.js index 4e0e522..519cf79 100644 --- a/static/js/Editor.js +++ b/static/js/Editor.js @@ -4,7 +4,7 @@ function Editor( id, notebook_id, note_text, deleted_from_id, revision, read_wri this.initial_text = note_text; this.deleted_from_id = deleted_from_id || null; this.revision = revision; - this.revisions_list = new Array(); // cache for this note's list of revisions, loaded from the server on-demand + this.user_revisions = new Array(); // cache for this note's list of revisions, loaded from the server on-demand this.read_write = read_write; this.startup = startup || false; // whether this Editor is for a startup note this.init_highlight = highlight || false; diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 5ba3bf5..c369336 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -1135,11 +1135,11 @@ Wiki.prototype.update_editor_revisions = function ( result, editor ) { return; var client_previous_revision = editor.revision; - editor.revision = result.new_revision; + editor.revision = result.new_revision.revision; // 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 != client_previous_revision ) { + if ( result.previous_revision.revision != client_previous_revision ) { var compare_button = createDOM( "input", { "type": "button", "class": "message_button", @@ -1150,18 +1150,18 @@ Wiki.prototype.update_editor_revisions = function ( result, editor ) { var self = this; connect( compare_button, "onclick", function ( event ) { - self.compare_versions( event, editor, result.previous_revision ); + self.compare_versions( event, editor, result.previous_revision.revision ); } ); - if ( !editor.revisions_list || editor.revisions_list.length == 0 ) + if ( !editor.user_revisions || editor.user_revisions.length == 0 ) return; - editor.revisions_list.push( result.previous_revision ); + editor.user_revisions.push( result.previous_revision ); } // add the new revision to the editor's revisions list - if ( !editor.revisions_list || editor.revisions_list.length == 0 ) + if ( !editor.user_revisions || editor.user_revisions.length == 0 ) return; - editor.revisions_list.push( result.new_revision ); + editor.user_revisions.push( result.new_revision ); } Wiki.prototype.search = function ( event ) { @@ -1341,8 +1341,15 @@ Wiki.prototype.display_invites = function ( invite_area ) { var owners = createDOM( "div", { "id": "owners" } ); var self = this; + var addresses = new Array(); + for ( var i in this.invites ) { var invite = this.invites[ i ]; + + // only display the first invite for a given email address + if ( addresses[ invite.email_address ] == true ) + continue; + var revoke_button = createDOM( "input", { "type": "button", "id": "revoke_" + invite.object_id, @@ -1363,8 +1370,12 @@ Wiki.prototype.display_invites = function ( invite_area ) { appendChildNodes( add_invite_to, createDOM( "div", { "class": "invite" }, - invite.email_address, " ", revoke_button ) + invite.email_address, " ", + ( invite.redeemed_username ? "(" + invite.redeemed_username + ")" : "" ), + " ", revoke_button ) ); + + addresses[ invite.email_address ] = true; } var div = createDOM( "div" ); @@ -1781,7 +1792,7 @@ Wiki.prototype.toggle_editor_changes = function ( event, editor ) { // if there's already a cached revision list, or the editor doesn't have a revision yet, then // display the changes pulldown and bail - if ( ( editor.revisions_list && editor.revisions_list.length > 0 ) || !editor.revision ) { + if ( ( editor.user_revisions && editor.user_revisions.length > 0 ) || !editor.revision ) { new Changes_pulldown( this, this.notebook_id, this.invoker, editor ); return; } @@ -1794,7 +1805,7 @@ Wiki.prototype.toggle_editor_changes = function ( event, editor ) { "note_id": editor.id }, function ( result ) { - editor.revisions_list = result.revisions; + editor.user_revisions = result.revisions; new Changes_pulldown( self, self.notebook_id, self.invoker, editor ); } ); @@ -1920,26 +1931,32 @@ function Changes_pulldown( wiki, notebook_id, invoker, editor ) { this.editor = editor; this.links = new Array(); - if ( !editor.revisions_list || editor.revisions_list.length == 0 ) { + if ( !editor.user_revisions || editor.user_revisions.length == 0 ) { appendChildNodes( this.div, createDOM( "span", "This note has no previous changes." ) ); return; } // display list of revision timestamps in reverse chronological order - var revisions_list = clone( editor.revisions_list ); - revisions_list.reverse(); + var user_revisions = clone( editor.user_revisions ); + user_revisions.reverse(); var self = this; - for ( var i = 0; i < revisions_list.length - 1; ++i ) { // -1 to skip the oldest revision - var revision = revisions_list[ i ]; - var short_revision = this.wiki.brief_revision( revision ); + for ( var i = 0; i < user_revisions.length - 1; ++i ) { // -1 to skip the oldest revision + var user_revision = user_revisions[ i ]; + var short_revision = this.wiki.brief_revision( user_revision.revision ); var href = "/notebooks/" + this.notebook_id + "?" + queryString( [ "note_id", "revision" ], - [ this.editor.id, revision ] + [ this.editor.id, user_revision.revision ] ); - var link = createDOM( "a", { "href": href, "class": "pulldown_link" }, short_revision ); + + var link = createDOM( + "a", + { "href": href, "class": "pulldown_link" }, + short_revision + ( user_revision.username ? " by " + user_revision.username : "" ) + ); + this.links.push( link ); - link.revision = revision; + link.revision = user_revision.revision; connect( link, "onclick", function ( event ) { self.link_clicked( event, self.editor.id ); } ); appendChildNodes( this.div, link ); appendChildNodes( this.div, createDOM( "br" ) );