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" ) );