Browse Source

* 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
Dan Helfman 10 years ago
parent
commit
5520fe5892

+ 3
- 1
NEWS View File

@@ -1,5 +1,7 @@
1 1
 1.1.0: January ??, 2007
2
- *
2
+ * Ability to invite people to your notebook as a collaborator or owner.
3
+ * Fixed bug where passwords with special characters broke password hashing.
4
+ * Fixed bug where link info box summaries sometimes contained HTML tags.
3 5
 
4 6
 1.0.4: December 30, 2007
5 7
  * Ability to invite people to view your notebook.

+ 17
- 13
controller/Notebooks.py View File

@@ -11,6 +11,7 @@ from model.Notebook import Notebook
11 11
 from model.Note import Note
12 12
 from model.Invite import Invite
13 13
 from model.User import User
14
+from model.User_revision import User_revision
14 15
 from view.Main_page import Main_page
15 16
 from view.Json import Json
16 17
 from view.Html_file import Html_file
@@ -374,7 +375,7 @@ class Notebooks( object ):
374 375
     @type user_id: unicode or NoneType
375 376
     @param user_id: id of current logged-in user (if any), determined by @grab_user_id
376 377
     @rtype: json dict
377
-    @return: { 'revisions': revisionslist or None }
378
+    @return: { 'revisions': userrevisionlist or None }
378 379
     @raise Access_error: the current user doesn't have access to the given notebook or note
379 380
     @raise Validation_error: one of the arguments is invalid
380 381
     """
@@ -398,7 +399,7 @@ class Notebooks( object ):
398 399
 
399 400
         raise Access_error()
400 401
 
401
-      revisions = self.__database.select_many( unicode, note.sql_load_revisions() )
402
+      revisions = self.__database.select_many( User_revision, note.sql_load_revisions() )
402 403
     else:
403 404
       revisions = None
404 405
 
@@ -438,8 +439,8 @@ class Notebooks( object ):
438 439
     @param user_id: id of current logged-in user (if any), determined by @grab_user_id
439 440
     @rtype: json dict
440 441
     @return: {
441
-      'new_revision': new revision of saved note, or None if nothing was saved,
442
-      'previous_revision': revision immediately before new_revision, or None if the note is new
442
+      'new_revision': User_revision of saved note, or None if nothing was saved
443
+      'previous_revision': User_revision immediately before new_revision, or None if the note is new
443 444
       'storage_bytes': current storage usage by user,
444 445
     }
445 446
     @raise Access_error: the current user doesn't have access to the given notebook
@@ -448,15 +449,16 @@ class Notebooks( object ):
448 449
     if not self.__users.check_access( user_id, notebook_id, read_write = True ):
449 450
       raise Access_error()
450 451
 
452
+    user = self.__database.load( User, user_id )
451 453
     notebook = self.__database.load( Notebook, notebook_id )
452 454
 
453
-    if not notebook:
455
+    if not user or not notebook:
454 456
       raise Access_error()
455 457
 
456 458
     note = self.__database.load( Note, note_id )
457 459
 
458 460
     # check whether the provided note contents have been changed since the previous revision
459
-    def update_note( current_notebook, old_note, startup, user_id ):
461
+    def update_note( current_notebook, old_note, startup, user ):
460 462
       # the note hasn't been changed, so bail without updating it
461 463
       if contents.replace( u"\n", u"" ) == old_note.contents.replace( u"\n", "" ) and startup == old_note.startup:
462 464
         new_revision = None
@@ -469,9 +471,9 @@ class Notebooks( object ):
469 471
             note.rank = self.__database.select_one( float, notebook.sql_highest_rank() ) + 1
470 472
         else:
471 473
           note.rank = None
472
-        note.user_id = user_id
474
+        note.user_id = user.object_id
473 475
 
474
-        new_revision = note.revision
476
+        new_revision = User_revision( note.revision, note.user_id, user.username )
475 477
 
476 478
       return new_revision
477 479
 
@@ -479,19 +481,21 @@ class Notebooks( object ):
479 481
     if note and note.notebook_id == notebook.object_id:
480 482
       old_note = self.__database.load( Note, note_id, previous_revision )
481 483
 
482
-      previous_revision = note.revision
483
-      new_revision = update_note( notebook, old_note, startup, user_id )
484
+      previous_user = self.__database.load( User, note.user_id )
485
+      previous_revision = User_revision( note.revision, note.user_id, previous_user and previous_user.username or None )
486
+      new_revision = update_note( notebook, old_note, startup, user )
484 487
 
485 488
     # the note is not already in the given notebook, so look for it in the trash
486 489
     elif note and notebook.trash_id and note.notebook_id == notebook.trash_id:
487 490
       old_note = self.__database.load( Note, note_id, previous_revision )
488 491
 
489 492
       # undelete the note, putting it back in the given notebook
490
-      previous_revision = note.revision
493
+      previous_user = self.__database.load( User, note.user_id )
494
+      previous_revision = User_revision( note.revision, note.user_id, previous_user and previous_user.username or None )
491 495
       note.notebook_id = notebook.object_id
492 496
       note.deleted_from_id = None
493 497
 
494
-      new_revision = update_note( notebook, old_note, startup, user_id )
498
+      new_revision = update_note( notebook, old_note, startup, user )
495 499
     # otherwise, create a new note
496 500
     else:
497 501
       if startup:
@@ -501,7 +505,7 @@ class Notebooks( object ):
501 505
   
502 506
       previous_revision = None
503 507
       note = Note.create( note_id, contents, notebook_id = notebook.object_id, startup = startup, rank = rank, user_id = user_id )
504
-      new_revision = note.revision
508
+      new_revision = User_revision( note.revision, note.user_id, user.username )
505 509
 
506 510
     if new_revision:
507 511
       self.__database.save( note, commit = False )

+ 13
- 1
controller/test/Test_controller.py View File

@@ -13,6 +13,7 @@ class Test_controller( object ):
13 13
     from model.Notebook import Notebook
14 14
     from model.Note import Note
15 15
     from model.Invite import Invite
16
+    from model.User_revision import User_revision
16 17
 
17 18
     # Since Stub_database isn't a real database and doesn't know SQL, replace some of the
18 19
     # SQL-returning methods in User, Note, and Notebook to return functions that manipulate data in
@@ -141,8 +142,19 @@ class Test_controller( object ):
141 142
     def sql_load_revisions( self, database ):
142 143
       note_list = database.objects.get( self.object_id )
143 144
       if not note_list: return None
145
+      revisions = []
146
+
147
+      for note in note_list:
148
+        user_list = database.objects.get( note.user_id )
149
+        user_id = None
150
+        username = None 
151
+
152
+        if user_list:
153
+          user_id = user_list[ -1 ].object_id
154
+          username = user_list[ -1 ].username
155
+
156
+        revisions.append( User_revision( note.revision, user_id, username ) )
144 157
 
145
-      revisions = [ note.revision for note in note_list ]
146 158
       return revisions
147 159
 
148 160
     Note.sql_load_revisions = lambda self: \

+ 67
- 24
controller/test/Test_notebooks.py View File

@@ -686,7 +686,9 @@ class Test_notebooks( Test_controller ):
686 686
     revisions = result[ "revisions" ]
687 687
     assert revisions != None
688 688
     assert len( revisions ) == 1
689
-    assert revisions[ 0 ] == self.note.revision
689
+    assert revisions[ 0 ].revision == self.note.revision
690
+    assert revisions[ 0 ].user_id == self.user.object_id
691
+    assert revisions[ 0 ].username == self.username
690 692
 
691 693
   def test_save_note( self, startup = False ):
692 694
     self.login()
@@ -702,9 +704,14 @@ class Test_notebooks( Test_controller ):
702 704
       previous_revision = previous_revision,
703 705
     ), session_id = self.session_id )
704 706
 
705
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
706
-    current_revision = result[ "new_revision" ]
707
-    assert result[ "previous_revision" ] == previous_revision
707
+    assert result[ "new_revision" ]
708
+    assert result[ "new_revision" ].revision != previous_revision
709
+    assert result[ "new_revision" ].user_id == self.user.object_id
710
+    assert result[ "new_revision" ].username == self.username
711
+    current_revision = result[ "new_revision" ].revision
712
+    assert result[ "previous_revision" ].revision == previous_revision
713
+    assert result[ "previous_revision" ].user_id == self.user.object_id
714
+    assert result[ "previous_revision" ].username == self.username
708 715
     user = self.database.load( User, self.user.object_id )
709 716
     assert user.storage_bytes > 0
710 717
     assert result[ "storage_bytes" ] == user.storage_bytes
@@ -746,8 +753,12 @@ class Test_notebooks( Test_controller ):
746 753
     revisions = result[ "revisions" ]
747 754
     assert revisions != None
748 755
     assert len( revisions ) == 2
749
-    assert revisions[ 0 ] == previous_revision
750
-    assert revisions[ 1 ] == current_revision
756
+    assert revisions[ 0 ].revision == previous_revision
757
+    assert revisions[ 0 ].user_id == self.user.object_id
758
+    assert revisions[ 0 ].username == self.username
759
+    assert revisions[ 1 ].revision == current_revision
760
+    assert revisions[ 1 ].user_id == self.user.object_id
761
+    assert revisions[ 1 ].username == self.username
751 762
 
752 763
   def test_save_startup_note( self ):
753 764
     self.test_save_note( startup = True )
@@ -790,8 +801,13 @@ class Test_notebooks( Test_controller ):
790 801
       previous_revision = previous_revision,
791 802
     ), session_id = self.session_id )
792 803
 
793
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
794
-    assert result[ "previous_revision" ] == previous_revision
804
+    assert result[ "new_revision" ]
805
+    assert result[ "new_revision" ].revision != previous_revision
806
+    assert result[ "new_revision" ].user_id == self.user.object_id
807
+    assert result[ "new_revision" ].username == self.username
808
+    assert result[ "previous_revision" ].revision == previous_revision
809
+    assert result[ "previous_revision" ].user_id == self.user.object_id
810
+    assert result[ "previous_revision" ].username == self.username
795 811
     user = self.database.load( User, self.user.object_id )
796 812
     assert user.storage_bytes > 0
797 813
     assert result[ "storage_bytes" ] == user.storage_bytes
@@ -851,7 +867,7 @@ class Test_notebooks( Test_controller ):
851 867
     # now attempt to save over that note again without changing the contents
852 868
     user = self.database.load( User, self.user.object_id )
853 869
     previous_storage_bytes = user.storage_bytes
854
-    previous_revision = result[ "new_revision" ]
870
+    previous_revision = result[ "new_revision" ].revision
855 871
     result = self.http_post( "/notebooks/save_note/", dict(
856 872
       notebook_id = self.notebook.object_id,
857 873
       note_id = self.note.object_id,
@@ -862,7 +878,9 @@ class Test_notebooks( Test_controller ):
862 878
 
863 879
     # assert that the note wasn't actually updated the second time
864 880
     assert result[ "new_revision" ] == None
865
-    assert result[ "previous_revision" ] == previous_revision
881
+    assert result[ "previous_revision" ].revision == previous_revision
882
+    assert result[ "previous_revision" ].user_id == self.user.object_id
883
+    assert result[ "previous_revision" ].username == self.username
866 884
     user = self.database.load( User, self.user.object_id )
867 885
     assert user.storage_bytes == previous_storage_bytes
868 886
     assert result[ "storage_bytes" ] == 0
@@ -902,7 +920,7 @@ class Test_notebooks( Test_controller ):
902 920
     # now attempt to save over that note again without changing the contents
903 921
     user = self.database.load( User, self.user.object_id )
904 922
     previous_storage_bytes = user.storage_bytes
905
-    previous_revision = result[ "new_revision" ]
923
+    previous_revision = result[ "new_revision" ].revision
906 924
     result = self.http_post( "/notebooks/save_note/", dict(
907 925
       notebook_id = self.notebook.object_id,
908 926
       note_id = self.note.object_id,
@@ -913,7 +931,9 @@ class Test_notebooks( Test_controller ):
913 931
 
914 932
     # assert that the note wasn't actually updated the second time
915 933
     assert result[ "new_revision" ] == None
916
-    assert result[ "previous_revision" ] == previous_revision
934
+    assert result[ "previous_revision" ].revision == previous_revision
935
+    assert result[ "previous_revision" ].user_id == self.user.object_id
936
+    assert result[ "previous_revision" ].username == self.username
917 937
     user = self.database.load( User, self.user.object_id )
918 938
     assert user.storage_bytes == previous_storage_bytes
919 939
     assert result[ "storage_bytes" ] == 0
@@ -956,7 +976,7 @@ class Test_notebooks( Test_controller ):
956 976
 
957 977
     # now attempt to save over that note again without changing the contents, but with a change
958 978
     # to its startup flag
959
-    previous_revision = result[ "new_revision" ]
979
+    previous_revision = result[ "new_revision" ].revision
960 980
     result = self.http_post( "/notebooks/save_note/", dict(
961 981
       notebook_id = self.notebook.object_id,
962 982
       note_id = self.note.object_id,
@@ -966,8 +986,13 @@ class Test_notebooks( Test_controller ):
966 986
     ), session_id = self.session_id )
967 987
 
968 988
     # assert that the note was updated the second time
969
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
970
-    assert result[ "previous_revision" ] == previous_revision
989
+    assert result[ "new_revision" ]
990
+    assert result[ "new_revision" ].revision != previous_revision
991
+    assert result[ "new_revision" ].user_id == self.user.object_id
992
+    assert result[ "new_revision" ].username == self.username
993
+    assert result[ "previous_revision" ].revision == previous_revision
994
+    assert result[ "previous_revision" ].user_id == self.user.object_id
995
+    assert result[ "previous_revision" ].username == self.username
971 996
     user = self.database.load( User, self.user.object_id )
972 997
     assert user.storage_bytes > 0
973 998
     assert result[ "storage_bytes" ] == user.storage_bytes
@@ -1009,7 +1034,7 @@ class Test_notebooks( Test_controller ):
1009 1034
     # except for adding a newline
1010 1035
     user = self.database.load( User, self.user.object_id )
1011 1036
     previous_storage_bytes = user.storage_bytes
1012
-    previous_revision = result[ "new_revision" ]
1037
+    previous_revision = result[ "new_revision" ].revision
1013 1038
     result = self.http_post( "/notebooks/save_note/", dict(
1014 1039
       notebook_id = self.notebook.object_id,
1015 1040
       note_id = self.note.object_id,
@@ -1020,7 +1045,9 @@ class Test_notebooks( Test_controller ):
1020 1045
 
1021 1046
     # assert that the note wasn't actually updated the second time
1022 1047
     assert result[ "new_revision" ] == None
1023
-    assert result[ "previous_revision" ] == previous_revision
1048
+    assert result[ "previous_revision" ].revision == previous_revision
1049
+    assert result[ "previous_revision" ].user_id == self.user.object_id
1050
+    assert result[ "previous_revision" ].username == self.username
1024 1051
     user = self.database.load( User, self.user.object_id )
1025 1052
     assert user.storage_bytes == previous_storage_bytes
1026 1053
     assert result[ "storage_bytes" ] == 0
@@ -1054,7 +1081,7 @@ class Test_notebooks( Test_controller ):
1054 1081
 
1055 1082
     # save over that note again with new contents, providing the original
1056 1083
     # revision as the previous known revision
1057
-    second_revision = result[ "new_revision" ]
1084
+    second_revision = result[ "new_revision" ].revision
1058 1085
     new_note_contents = u"<h3>new new title</h3>new new blah"
1059 1086
     result = self.http_post( "/notebooks/save_note/", dict(
1060 1087
       notebook_id = self.notebook.object_id,
@@ -1066,8 +1093,12 @@ class Test_notebooks( Test_controller ):
1066 1093
 
1067 1094
     # make sure the second save actually caused an update
1068 1095
     assert result[ "new_revision" ]
1069
-    assert result[ "new_revision" ] not in ( first_revision, second_revision )
1070
-    assert result[ "previous_revision" ] == second_revision
1096
+    assert result[ "new_revision" ].revision not in ( first_revision, second_revision )
1097
+    assert result[ "new_revision" ].user_id == self.user.object_id
1098
+    assert result[ "new_revision" ].username == self.username
1099
+    assert result[ "previous_revision" ].revision == second_revision
1100
+    assert result[ "previous_revision" ].user_id == self.user.object_id
1101
+    assert result[ "previous_revision" ].username == self.username
1071 1102
     user = self.database.load( User, self.user.object_id )
1072 1103
     assert user.storage_bytes > 0
1073 1104
     assert result[ "storage_bytes" ] == user.storage_bytes
@@ -1133,7 +1164,10 @@ class Test_notebooks( Test_controller ):
1133 1164
       previous_revision = None,
1134 1165
     ), session_id = self.session_id )
1135 1166
 
1136
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
1167
+    assert result[ "new_revision" ]
1168
+    assert result[ "new_revision" ] != previous_revision
1169
+    assert result[ "new_revision" ].user_id == self.user.object_id
1170
+    assert result[ "new_revision" ].username == self.username
1137 1171
     assert result[ "previous_revision" ] == None
1138 1172
     user = self.database.load( User, self.user.object_id )
1139 1173
     assert user.storage_bytes > 0
@@ -1179,7 +1213,10 @@ class Test_notebooks( Test_controller ):
1179 1213
       previous_revision = None,
1180 1214
     ), session_id = self.session_id )
1181 1215
 
1182
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
1216
+    assert result[ "new_revision" ]
1217
+    assert result[ "new_revision" ] != previous_revision
1218
+    assert result[ "new_revision" ].user_id == self.user.object_id
1219
+    assert result[ "new_revision" ].username == self.username
1183 1220
     assert result[ "previous_revision" ] == None
1184 1221
     user = self.database.load( User, self.user.object_id )
1185 1222
     assert user.storage_bytes > 0
@@ -1216,7 +1253,10 @@ class Test_notebooks( Test_controller ):
1216 1253
       previous_revision = None,
1217 1254
     ), session_id = self.session_id )
1218 1255
 
1219
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
1256
+    assert result[ "new_revision" ]
1257
+    assert result[ "new_revision" ] != previous_revision
1258
+    assert result[ "new_revision" ].user_id == self.user.object_id
1259
+    assert result[ "new_revision" ].username == self.username
1220 1260
     assert result[ "previous_revision" ] == None
1221 1261
     user = self.database.load( User, self.user.object_id )
1222 1262
     assert user.storage_bytes > 0
@@ -1263,7 +1303,10 @@ class Test_notebooks( Test_controller ):
1263 1303
       previous_revision = None,
1264 1304
     ), session_id = self.session_id )
1265 1305
 
1266
-    assert result[ "new_revision" ] and result[ "new_revision" ] != previous_revision
1306
+    assert result[ "new_revision" ]
1307
+    assert result[ "new_revision" ] != previous_revision
1308
+    assert result[ "new_revision" ].user_id == self.user.object_id
1309
+    assert result[ "new_revision" ].username == self.username
1267 1310
     assert result[ "previous_revision" ] == None
1268 1311
     user = self.database.load( User, self.user.object_id )
1269 1312
     assert user.storage_bytes > 0

+ 46
- 9
model/Invite.py View File

@@ -6,7 +6,8 @@ class Invite( Persistent ):
6 6
   An invitiation to view or edit a notebook.
7 7
   """
8 8
   def __init__( self, object_id, revision = None, from_user_id = None, notebook_id = None,
9
-                email_address = None, read_write = False, owner = False, redeemed_user_id = None ):
9
+                email_address = None, read_write = False, owner = False, redeemed_user_id = None,
10
+                redeemed_username = None ):
10 11
     """
11 12
     Create an invitation with the given id.
12 13
 
@@ -26,6 +27,8 @@ class Invite( Persistent ):
26 27
     @param owner: whether the invitation is for owner-level access (optional, defaults to False)
27 28
     @type redeemed_user_id: unicode or NoneType
28 29
     @param redeemed_user_id: id of the user who has redeemed this invitation, if any (optional)
30
+    @type redeemed_username: unicode or NoneType
31
+    @param redeemed_username: username of the user who has redeemed this invitation, if any (optional)
29 32
     @rtype: Invite
30 33
     @return: newly constructed notebook invitation
31 34
     """
@@ -36,6 +39,7 @@ class Invite( Persistent ):
36 39
     self.__read_write = read_write
37 40
     self.__owner = owner
38 41
     self.__redeemed_user_id = redeemed_user_id
42
+    self.__redeemed_username = redeemed_username
39 43
 
40 44
   @staticmethod
41 45
   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 ):
66 70
     if revision:
67 71
       raise NotImplementedError()
68 72
 
69
-    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 )
73
+    return \
74
+      """
75
+      select
76
+        invite.id, invite.revision, invite.from_user_id, invite.notebook_id, invite.email_address,
77
+        invite.read_write, invite.owner, invite.redeemed_user_id, luminotes_user_current.username
78
+      from
79
+        invite left outer join luminotes_user_current
80
+      on
81
+        ( invite.redeemed_user_id = luminotes_user_current.id )
82
+      where
83
+        invite.id = %s;
84
+      """ % quote( object_id )
70 85
 
71 86
   @staticmethod
72 87
   def sql_id_exists( object_id, revision = None ):
@@ -92,16 +107,36 @@ class Invite( Persistent ):
92 107
 
93 108
   def sql_load_similar( self ):
94 109
     # select invites with the same notebook_id, and email_address as this invite
95
-    return "select id, revision, from_user_id, notebook_id, email_address, read_write, owner, redeemed_user_id from invite " + \
96
-           "where notebook_id = %s and email_address = %s and id != %s;" % \
97
-           ( quote( self.__notebook_id ), quote( self.__email_address ), quote( self.object_id ) )
110
+    return \
111
+      """
112
+      select
113
+        invite.id, invite.revision, invite.from_user_id, invite.notebook_id, invite.email_address,
114
+        invite.read_write, invite.owner, invite.redeemed_user_id, luminotes_user_current.username
115
+      from
116
+        invite left outer join luminotes_user_current
117
+      on
118
+        ( invite.redeemed_user_id = luminotes_user_current.id )
119
+      where
120
+        invite.notebook_id = %s and invite.email_address = %s and invite.id != %s;
121
+      """ % ( quote( self.__notebook_id ), quote( self.__email_address ), quote( self.object_id ) )
98 122
 
99 123
   @staticmethod
100 124
   def sql_load_notebook_invites( notebook_id ):
101
-    # select a list of invites to the given notebook. if there are multiple invites for a given
102
-    # email_address, arbitrarily pick one of them
103
-    return "select id, revision, from_user_id, notebook_id, email_address, read_write, owner, redeemed_user_id from invite " + \
104
-           "where id in ( select max( id ) from invite where notebook_id = %s group by email_address ) order by email_address;" % quote( notebook_id )
125
+    # select a list of invites to the given notebook
126
+    return \
127
+      """
128
+      select
129
+        invite.id, invite.revision, invite.from_user_id, invite.notebook_id, invite.email_address,
130
+        invite.read_write, invite.owner, invite.redeemed_user_id, luminotes_user_current.username
131
+      from
132
+        invite left outer join luminotes_user_current
133
+      on
134
+        ( invite.redeemed_user_id = luminotes_user_current.id )
135
+      where
136
+        invite.notebook_id = %s
137
+      order by
138
+        invite.email_address, invite.redeemed_user_id;
139
+      """ % quote( notebook_id )
105 140
 
106 141
   def sql_revoke_invites( self ):
107 142
     return "delete from invite where notebook_id = %s and email_address = %s;" % \
@@ -116,6 +151,7 @@ class Invite( Persistent ):
116 151
       read_write = self.__read_write,
117 152
       owner = self.__owner,
118 153
       redeemed_user_id = self.__redeemed_user_id,
154
+      redeemed_username = self.__redeemed_username,
119 155
     ) )
120 156
 
121 157
     return d
@@ -141,3 +177,4 @@ class Invite( Persistent ):
141 177
   read_write = property( lambda self: self.__read_write, __set_read_write )
142 178
   owner = property( lambda self: self.__owner, __set_owner )
143 179
   redeemed_user_id = property( lambda self: self.__redeemed_user_id, __set_redeemed_user_id )
180
+  redeemed_username = property( lambda self: self.__redeemed_username )

+ 10
- 1
model/Note.py View File

@@ -152,7 +152,16 @@ class Note( Persistent ):
152 152
     return self.sql_create()
153 153
 
154 154
   def sql_load_revisions( self ):
155
-    return "select revision from note where id = %s order by revision;" % quote( self.object_id )
155
+    return """ \
156
+      select
157
+        note.revision, luminotes_user_current.id, username
158
+      from
159
+        note left outer join luminotes_user_current
160
+      on
161
+        ( note.user_id = luminotes_user_current.id )
162
+      where
163
+        note.id = %s order by note.revision;
164
+    """ % quote( self.object_id )
156 165
 
157 166
   def to_dict( self ):
158 167
     d = Persistent.to_dict( self )

+ 30
- 0
model/User_revision.py View File

@@ -0,0 +1,30 @@
1
+class User_revision( object ):
2
+  """
3
+  A revision timestamp along with information on the user that made that revision.
4
+  """
5
+  def __init__( self, revision, user_id = None, username = None ):
6
+    """
7
+    Create a User_revision with the given timestamp and user information.
8
+
9
+    @type revision: datetime
10
+    @param revision: revision timestamp
11
+    @type user_id: unicode or NoneType
12
+    @param user_id: id of user who made this revision (optional, defaults to None)
13
+    @type username: username of user who made this revision (optional, defaults to None)
14
+    @rtype: User_revision
15
+    @return: newly constructed User_revision object
16
+    """
17
+    self.__revision = revision
18
+    self.__user_id = user_id
19
+    self.__username = username
20
+
21
+  def to_dict( self ):
22
+    return dict(
23
+      revision = self.__revision,
24
+      user_id = self.__user_id,
25
+      username = self.__username,
26
+    )
27
+
28
+  revision = property( lambda self: self.__revision )
29
+  user_id = property( lambda self: self.__user_id )
30
+  username = property( lambda self: self.__username )

+ 6
- 11
model/delta/1.1.0.sql View File

@@ -1,11 +1,6 @@
1
-update notebook set user_id = (
2
-  select user_notebook.user_id
3
-    from user_notebook
4
-    where notebook_id = notebook.id and read_write = 't' and owner = 't'
5
-    limit 1
6
-);
7
-update note set user_id = (
8
-  select notebook_current.user_id
9
-    from notebook_current
10
-    where note.notebook_id = notebook_current.id
11
-);
1
+update notebook set user_id = user_notebook.user_id
2
+  from user_notebook
3
+  where user_notebook.notebook_id = notebook.id and read_write = 't' and owner = 't';
4
+update note set user_id = notebook_current.user_id
5
+  from notebook_current
6
+  where note.notebook_id = notebook_current.id;

+ 2
- 0
model/test/Test_invite.py View File

@@ -25,6 +25,7 @@ class Test_invite( object ):
25 25
     assert self.invite.read_write == self.read_write
26 26
     assert self.invite.owner == self.owner
27 27
     assert self.invite.redeemed_user_id == None
28
+    assert self.invite.redeemed_username == None
28 29
 
29 30
   def test_redeem( self ):
30 31
     previous_revision = self.invite.revision
@@ -53,3 +54,4 @@ class Test_invite( object ):
53 54
     assert d.get( "read_write" ) == self.read_write
54 55
     assert d.get( "owner" ) == self.owner
55 56
     assert d.get( "redeemed_user_id" ) == None
57
+    assert d.get( "redeemed_username" ) == None

+ 9
- 0
model/test/Test_user.py View File

@@ -36,6 +36,15 @@ class Test_user( object ):
36 36
     assert self.user.check_password( new_password ) == True
37 37
     assert self.user.revision > previous_revision
38 38
 
39
+  def test_set_password_with_special_characters( self ):
40
+    previous_revision = self.user.revision
41
+    new_password = u"newpass\xe4"
42
+    self.user.password = new_password
43
+
44
+    assert self.user.check_password( self.password ) == False
45
+    assert self.user.check_password( new_password ) == True
46
+    assert self.user.revision > previous_revision
47
+
39 48
   def test_set_none_password( self ):
40 49
     previous_revision = self.user.revision
41 50
     new_password = None

+ 23
- 0
model/test/Test_user_revision.py View File

@@ -0,0 +1,23 @@
1
+from datetime import datetime
2
+from model.User_revision import User_revision
3
+
4
+
5
+class Test_user_revision( object ):
6
+  def setUp( self ):
7
+    self.revision = datetime.now()
8
+    self.user_id = u"77"
9
+    self.username = u"bob"
10
+
11
+    self.user_revision = User_revision( self.revision, self.user_id, self.username )
12
+
13
+  def test_create( self ):
14
+    assert self.user_revision.revision == self.revision
15
+    assert self.user_revision.user_id == self.user_id
16
+    assert self.user_revision.username == self.username
17
+
18
+  def test_to_dict( self ):
19
+    d = self.user_revision.to_dict()
20
+
21
+    assert d.get( "revision" ) == self.revision
22
+    assert d.get( "user_id" ) == self.user_id
23
+    assert d.get( "username" ) == self.username

+ 1
- 1
static/js/Editor.js View File

@@ -4,7 +4,7 @@ function Editor( id, notebook_id, note_text, deleted_from_id, revision, read_wri
4 4
   this.initial_text = note_text;
5 5
   this.deleted_from_id = deleted_from_id || null;
6 6
   this.revision = revision;
7
-  this.revisions_list = new Array(); // cache for this note's list of revisions, loaded from the server on-demand
7
+  this.user_revisions = new Array(); // cache for this note's list of revisions, loaded from the server on-demand
8 8
   this.read_write = read_write;
9 9
   this.startup = startup || false; // whether this Editor is for a startup note
10 10
   this.init_highlight = highlight || false;

+ 36
- 19
static/js/Wiki.js View File

@@ -1135,11 +1135,11 @@ Wiki.prototype.update_editor_revisions = function ( result, editor ) {
1135 1135
     return;
1136 1136
 
1137 1137
   var client_previous_revision = editor.revision;
1138
-  editor.revision = result.new_revision;
1138
+  editor.revision = result.new_revision.revision;
1139 1139
 
1140 1140
   // if the server's idea of the previous revision doesn't match the client's, then someone has
1141 1141
   // gone behind our back and saved the editor's note from another window
1142
-  if ( result.previous_revision != client_previous_revision ) {
1142
+  if ( result.previous_revision.revision != client_previous_revision ) {
1143 1143
     var compare_button = createDOM( "input", {
1144 1144
       "type": "button",
1145 1145
       "class": "message_button",
@@ -1150,18 +1150,18 @@ Wiki.prototype.update_editor_revisions = function ( result, editor ) {
1150 1150
 
1151 1151
     var self = this;
1152 1152
     connect( compare_button, "onclick", function ( event ) {
1153
-      self.compare_versions( event, editor, result.previous_revision );
1153
+      self.compare_versions( event, editor, result.previous_revision.revision );
1154 1154
     } );
1155 1155
 
1156
-    if ( !editor.revisions_list || editor.revisions_list.length == 0 )
1156
+    if ( !editor.user_revisions || editor.user_revisions.length == 0 )
1157 1157
       return;
1158
-    editor.revisions_list.push( result.previous_revision );
1158
+    editor.user_revisions.push( result.previous_revision );
1159 1159
   }
1160 1160
 
1161 1161
   // add the new revision to the editor's revisions list
1162
-  if ( !editor.revisions_list || editor.revisions_list.length == 0 )
1162
+  if ( !editor.user_revisions || editor.user_revisions.length == 0 )
1163 1163
     return;
1164
-  editor.revisions_list.push( result.new_revision );
1164
+  editor.user_revisions.push( result.new_revision );
1165 1165
 }
1166 1166
 
1167 1167
 Wiki.prototype.search = function ( event ) {
@@ -1341,8 +1341,15 @@ Wiki.prototype.display_invites = function ( invite_area ) {
1341 1341
   var owners = createDOM( "div", { "id": "owners" } );
1342 1342
   var self = this;
1343 1343
 
1344
+  var addresses = new Array();
1345
+
1344 1346
   for ( var i in this.invites ) {
1345 1347
     var invite = this.invites[ i ];
1348
+
1349
+    // only display the first invite for a given email address
1350
+    if ( addresses[ invite.email_address ] == true )
1351
+      continue;
1352
+
1346 1353
     var revoke_button = createDOM( "input", {
1347 1354
       "type": "button",
1348 1355
       "id": "revoke_" + invite.object_id,
@@ -1363,8 +1370,12 @@ Wiki.prototype.display_invites = function ( invite_area ) {
1363 1370
 
1364 1371
     appendChildNodes(
1365 1372
       add_invite_to, createDOM( "div", { "class": "invite" },
1366
-      invite.email_address, " ", revoke_button )
1373
+      invite.email_address, " ",
1374
+      ( invite.redeemed_username ? "(" + invite.redeemed_username + ")" : "" ),
1375
+      " ", revoke_button )
1367 1376
     );
1377
+
1378
+    addresses[ invite.email_address ] = true;
1368 1379
   }
1369 1380
 
1370 1381
   var div = createDOM( "div" );
@@ -1781,7 +1792,7 @@ Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
1781 1792
 
1782 1793
   // if there's already a cached revision list, or the editor doesn't have a revision yet, then
1783 1794
   // display the changes pulldown and bail
1784
-  if ( ( editor.revisions_list && editor.revisions_list.length > 0 ) || !editor.revision ) {
1795
+  if ( ( editor.user_revisions && editor.user_revisions.length > 0 ) || !editor.revision ) {
1785 1796
     new Changes_pulldown( this, this.notebook_id, this.invoker, editor );
1786 1797
     return;
1787 1798
   }
@@ -1794,7 +1805,7 @@ Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
1794 1805
       "note_id": editor.id
1795 1806
     },
1796 1807
     function ( result ) {
1797
-      editor.revisions_list = result.revisions;
1808
+      editor.user_revisions = result.revisions;
1798 1809
       new Changes_pulldown( self, self.notebook_id, self.invoker, editor );
1799 1810
     }
1800 1811
   );
@@ -1920,26 +1931,32 @@ function Changes_pulldown( wiki, notebook_id, invoker, editor ) {
1920 1931
   this.editor = editor;
1921 1932
   this.links = new Array();
1922 1933
   
1923
-  if ( !editor.revisions_list || editor.revisions_list.length == 0 ) {
1934
+  if ( !editor.user_revisions || editor.user_revisions.length == 0 ) {
1924 1935
     appendChildNodes( this.div, createDOM( "span", "This note has no previous changes." ) );
1925 1936
     return;
1926 1937
   }
1927 1938
 
1928 1939
   // display list of revision timestamps in reverse chronological order
1929
-  var revisions_list = clone( editor.revisions_list );
1930
-  revisions_list.reverse();
1940
+  var user_revisions = clone( editor.user_revisions );
1941
+  user_revisions.reverse();
1931 1942
 
1932 1943
   var self = this;
1933
-  for ( var i = 0; i < revisions_list.length - 1; ++i ) { // -1 to skip the oldest revision
1934
-    var revision = revisions_list[ i ];
1935
-    var short_revision = this.wiki.brief_revision( revision );
1944
+  for ( var i = 0; i < user_revisions.length - 1; ++i ) { // -1 to skip the oldest revision
1945
+    var user_revision = user_revisions[ i ];
1946
+    var short_revision = this.wiki.brief_revision( user_revision.revision );
1936 1947
     var href = "/notebooks/" + this.notebook_id + "?" + queryString(
1937 1948
       [ "note_id", "revision" ],
1938
-      [ this.editor.id, revision ]
1949
+      [ this.editor.id, user_revision.revision ]
1950
+    );
1951
+
1952
+    var link = createDOM(
1953
+      "a",
1954
+      { "href": href, "class": "pulldown_link" },
1955
+      short_revision + ( user_revision.username ? " by " + user_revision.username : "" )
1939 1956
     );
1940
-    var link = createDOM( "a", { "href": href, "class": "pulldown_link" }, short_revision );
1957
+
1941 1958
     this.links.push( link );
1942
-    link.revision = revision;
1959
+    link.revision = user_revision.revision;
1943 1960
     connect( link, "onclick", function ( event ) { self.link_clicked( event, self.editor.id ); } );
1944 1961
     appendChildNodes( this.div, link );
1945 1962
     appendChildNodes( this.div, createDOM( "br" ) );

Loading…
Cancel
Save