Browse Source

Foundational work for both tags and discussion forums. Should have checked this in in smaller pieces.

Dan Helfman 9 years ago
parent
commit
388f2fcb02

+ 8
- 0
NEWS View File

@@ -1,3 +1,11 @@
1
+1.5.5: 
2
+ * Improved speed of Luminotes Desktop by adding some database indices. This
3
+   will help in particular for larger notebooks with many notes.
4
+ * Added some code to automatically upgrade your database when upgrading to a
5
+   new Luminotes release. This applies to all Luminotes products.
6
+ * Added code to support Luminotes discussion forums.
7
+ * Laid some of the foundational groundwork for future tags support.
8
+
1 9
 1.5.4: October 9, 2008
2 10
  * Fixed a visual bug in which clicking up or down to reorder your notebooks
3 11
    didn't display correctly.

+ 32
- 6
UPGRADE View File

@@ -1,18 +1,44 @@
1
+When upgrading Luminotes, if you are using memcached, it is recommended
2
+that you restart memcached to clear your cache.
3
+
4
+
5
+Upgrading from Luminotes 1.5.0 or higher
6
+----------------------------------------
7
+
8
+If you're using Luminotes 1.5.0 or higher and you'd like to upgrade to a
9
+newer version, Luminotes will automatically upgrade your database when
10
+you start Luminotes after an upgrade. This means that all of your notes
11
+and notebooks created in an older versions of Luminotes will be included
12
+in the upgrade. You don't have to do a thing other than install the
13
+software for the new release, and then execute the following command:
14
+
15
+  export PYTHONPATH=.
16
+  python2.4 tools/updatedb.py
17
+
18
+
19
+Upgrading from Luminotes 1.0, 1.2, 1.3, or 1.4
20
+----------------------------------------------
21
+
22
+If you're using an older version of Luminotes (prior to 1.5.0) and you'd
23
+like to upgrade to a newer version, you'll have to perform database
24
+upgrades manually. Below are the intructions for doing so.
25
+
1 26
 To upgrade the Luminotes database from an earlier version, manually apply each
2 27
 relevant schema delta file within model/delta/
3 28
 
4
-For instance, if you were upgrading from version 5.0.1 to 5.0.4, you would
29
+For instance, if you are upgrading from version 1.3.12 to 1.5.0, you would
5 30
 apply the following deltas in order:
6 31
 
7
-  psql -U luminotes luminotes -f model/delta/5.0.2.sql
8
-  psql -U luminotes luminotes -f model/delta/5.0.3.sql
9
-  psql -U luminotes luminotes -f model/delta/5.0.4.sql
32
+  psql -U luminotes luminotes -f model/delta/1.3.14.sql
33
+  psql -U luminotes luminotes -f model/delta/1.4.0.sql
34
+  psql -U luminotes luminotes -f model/delta/1.5.0.sql
10 35
 
11 36
 Any version which does not introduce a schema change does not have a
12 37
 corresponding schema delta file.
13 38
 
14
-Sometimes I include comments within a schema delta file with additional
15
-manual steps you need to take.
39
+IMPORTANT: Even if you are upgrading past version 1.5.0 to a newer version,
40
+you should stop applying schema delta files after 1.5.0. This is because the
41
+Luminotes automatic schema upgrade process will pick up after that point.
16 42
 
17 43
 After you've updated the schema, run the updatedb.py script:
18 44
 

+ 1
- 1
config/Version.py View File

@@ -1 +1 @@
1
-VERSION = u"1.5.4"
1
+VERSION = u"1.5.5"

+ 11
- 11
controller/Files.py View File

@@ -290,7 +290,7 @@ class Files( object ):
290 290
     """
291 291
     db_file = self.__database.load( File, file_id )
292 292
 
293
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
293
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
294 294
       raise Access_error()
295 295
 
296 296
     # if the file is openable as an image, then allow the user to view it instead of downloading it
@@ -396,7 +396,7 @@ class Files( object ):
396 396
     """
397 397
     db_file = self.__database.load( File, file_id )
398 398
 
399
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
399
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
400 400
       raise Access_error()
401 401
 
402 402
     filename = db_file.filename.replace( '"', r"\"" )
@@ -432,7 +432,7 @@ class Files( object ):
432 432
     """
433 433
     db_file = self.__database.load( File, file_id )
434 434
 
435
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
435
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
436 436
       raise Access_error()
437 437
 
438 438
     cherrypy.response.headerMap[ u"Content-Type" ] = u"image/png"
@@ -491,7 +491,7 @@ class Files( object ):
491 491
     """
492 492
     db_file = self.__database.load( File, file_id )
493 493
 
494
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
494
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
495 495
       raise Access_error()
496 496
 
497 497
     cherrypy.response.headerMap[ u"Content-Type" ] = db_file.content_type
@@ -531,7 +531,7 @@ class Files( object ):
531 531
     @return: rendered HTML page
532 532
     @raise Access_error: the current user doesn't have access to the given notebook
533 533
     """
534
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
534
+    if not self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id ):
535 535
       raise Access_error()
536 536
 
537 537
     file_id = self.__database.next_id( File )
@@ -565,7 +565,7 @@ class Files( object ):
565 565
     @return: rendered HTML page
566 566
     @raise Access_error: the current user doesn't have access to the given notebook
567 567
     """
568
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
568
+    if not self.__users.load_notebook( user_id, notebook_id, read_write = True ):
569 569
       raise Access_error()
570 570
 
571 571
     file_id = self.__database.next_id( File )
@@ -622,7 +622,7 @@ class Files( object ):
622 622
       current_uploads_lock.release()
623 623
 
624 624
     user = self.__database.load( User, user_id )
625
-    if not user or not self.__users.check_access( user_id, notebook_id, read_write = True ):
625
+    if not user or not self.__users.load_notebook( user_id, notebook_id, read_write = True ):
626 626
       uploaded_file.delete()
627 627
       return dict( script = general_error_script % u"Sorry, you don't have access to do that. Please make sure you're logged in as the correct user." )
628 628
 
@@ -739,7 +739,7 @@ class Files( object ):
739 739
     """
740 740
     db_file = self.__database.load( File, file_id )
741 741
 
742
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
742
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
743 743
       raise Access_error()
744 744
 
745 745
     user = self.__database.load( User, user_id )
@@ -778,7 +778,7 @@ class Files( object ):
778 778
     """
779 779
     db_file = self.__database.load( File, file_id )
780 780
 
781
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id, read_write = True ):
781
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id, read_write = True ):
782 782
       raise Access_error()
783 783
 
784 784
     self.__database.execute( db_file.sql_delete(), commit = False )
@@ -817,7 +817,7 @@ class Files( object ):
817 817
     """
818 818
     db_file = self.__database.load( File, file_id )
819 819
 
820
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id, read_write = True ):
820
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id, read_write = True ):
821 821
       raise Access_error()
822 822
 
823 823
     db_file.filename = filename
@@ -919,7 +919,7 @@ class Files( object ):
919 919
 
920 920
     db_file = self.__database.load( File, file_id )
921 921
 
922
-    if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
922
+    if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
923 923
       raise Access_error()
924 924
 
925 925
     parser = self.parse_csv( file_id )

+ 60
- 2
controller/Forums.py View File

@@ -1,26 +1,35 @@
1
+import cherrypy
2
+from model.User import User
3
+from model.Notebook import Notebook
1 4
 from Expose import expose
2
-from Validate import validate
5
+from Validate import validate, Valid_string
3 6
 from Database import Valid_id, end_transaction
4 7
 from Users import grab_user_id
8
+from Notebooks import Notebooks
5 9
 from view.Forums_page import Forums_page
10
+from view.Forum_page import Forum_page
11
+from view.Main_page import Main_page
6 12
 
7 13
 
8 14
 class Forums( object ):
9 15
   """
10 16
   Controller for dealing with discussion forums, corresponding to the "/forums" URL.
11 17
   """
12
-  def __init__( self, database, users ):
18
+  def __init__( self, database, notebooks, users ):
13 19
     """
14 20
     Create a new Forums object.
15 21
 
16 22
     @type database: controller.Database
17 23
     @param database: database that forums are stored in
24
+    @type notebooks: controller.Users
25
+    @param notebooks: controller for all notebooks
18 26
     @type users: controller.Users
19 27
     @param users: controller for all users
20 28
     @rtype: Forums
21 29
     @return: newly constructed Forums
22 30
     """
23 31
     self.__database = database
32
+    self.__notebooks = notebooks
24 33
     self.__users = users
25 34
 
26 35
   @expose( view = Forums_page )
@@ -32,6 +41,34 @@ class Forums( object ):
32 41
   def index( self, user_id ):
33 42
     """
34 43
     Provide the information necessary to display the listing of available forums (currently hard-coded).
44
+
45
+    @type user_id: unicode or NoneType
46
+    @param user_id: id of the current user
47
+    """
48
+    result = self.__users.current( user_id )
49
+    parents = [ notebook for notebook in result[ u"notebooks" ] if notebook.trash_id and not notebook.deleted ]
50
+    if len( parents ) > 0:
51
+      result[ "first_notebook" ] = parents[ 0 ]
52
+    else:
53
+      result[ "first_notebook" ] = None
54
+
55
+    return result
56
+
57
+  @expose( view = Forum_page )
58
+  @end_transaction
59
+  @grab_user_id
60
+  @validate(
61
+    forum_name = Valid_string( max = 100 ),
62
+    user_id = Valid_id( none_okay = True ),
63
+  )
64
+  def default( self, forum_name, user_id ):
65
+    """
66
+    Provide the information necessary to display the current threads within a forum.
67
+
68
+    @type forum_name: unicode
69
+    @param forum_name: name of the forum to display
70
+    @type user_id: unicode or NoneType
71
+    @param user_id: id of the current user
35 72
     """
36 73
     result = self.__users.current( user_id )
37 74
     parents = [ notebook for notebook in result[ u"notebooks" ] if notebook.trash_id and not notebook.deleted ]
@@ -40,4 +77,25 @@ class Forums( object ):
40 77
     else:
41 78
       result[ "first_notebook" ] = None
42 79
 
80
+    anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
81
+    if anonymous is None:
82
+      raise Access_error()
83
+
84
+    # TODO: this needs to sort by either thread/note creation or modification date
85
+    threads = self.__database.select_many(
86
+      Notebook,
87
+      anonymous.sql_load_notebooks(
88
+        parents_only = False, undeleted_only = True, tag_name = u"forum", tag_value = forum_name
89
+      )
90
+    )
91
+
92
+    # if there are no matching threads, then this forum doesn't exist
93
+    if len( threads ) == 0:
94
+      raise cherrypy.NotFound
95
+
96
+    result[ "forum_name" ] = forum_name
97
+    result[ "threads" ] = threads
43 98
     return result
99
+
100
+  # threads() is just an alias for Notebooks.default()
101
+  threads = Notebooks.default

+ 74
- 118
controller/Notebooks.py View File

@@ -135,7 +135,7 @@ class Notebooks( object ):
135 135
       ]
136 136
       if len( result[ u"notebooks" ] ) == 0:
137 137
         raise Access_error()
138
-      result[ u"notebooks" ][ 0 ].read_write = False
138
+      result[ u"notebooks" ][ 0 ].read_write = Notebook.READ_ONLY
139 139
       result[ u"notebooks" ][ 0 ].owner = False
140 140
     elif preview in ( u"owner", u"default", None ):
141 141
       read_write = True
@@ -148,9 +148,8 @@ class Notebooks( object ):
148 148
     if revision:
149 149
       result[ "note_read_write" ] = False
150 150
 
151
-    notebook = self.__database.load( Notebook, notebook_id )
152
-    if not notebook:
153
-      raise Access_error()
151
+    notebook = result[ u"notebook" ]
152
+
154 153
     if notebook.name != u"Luminotes":
155 154
       result[ "recent_notes" ] = self.__database.select_many( Note, notebook.sql_load_notes( start = 0, count = 10 ) )
156 155
 
@@ -181,9 +180,11 @@ class Notebooks( object ):
181 180
     @type previous_revision: unicode or NoneType
182 181
     @param previous_revision: older revision timestamp to diff with the given revision (optional)
183 182
     @type read_write: bool or NoneType
184
-    @param read_write: whether the notebook should be returned as read-write (optional, defaults to True)
183
+    @param read_write: whether the notebook should be returned as read-write (optional, defaults to True).
184
+                       this can only lower access, not elevate it
185 185
     @type owner: bool or NoneType
186
-    @param owner: whether the notebook should be returned as owner-level access (optional, defaults to True)
186
+    @param owner: whether the notebook should be returned as owner-level access (optional, defaults to True).
187
+                  this can only lower access, not elevate it
187 188
     @type user_id: unicode or NoneType
188 189
     @param user_id: id of current logged-in user (if any)
189 190
     @rtype: dict
@@ -197,23 +198,16 @@ class Notebooks( object ):
197 198
     @raise Access_error: the current user doesn't have access to the given notebook or note
198 199
     @raise Validation_error: one of the arguments is invalid
199 200
     """
200
-    if not self.__users.check_access( user_id, notebook_id ):
201
-      raise Access_error()
202
-
203
-    notebook = self.__database.load( Notebook, notebook_id )
201
+    notebook = self.__users.load_notebook( user_id, notebook_id )
204 202
 
205 203
     if notebook is None:
206 204
       raise Access_error()
207 205
 
208 206
     if read_write is False:
209
-      notebook.read_write = False
210
-    elif not self.__users.check_access( user_id, notebook_id, read_write = True ):
211
-      notebook.read_write = False
207
+      notebook.read_write = Notebook.READ_ONLY
212 208
 
213 209
     if owner is False:
214 210
       notebook.owner = False
215
-    elif not self.__users.check_access( user_id, notebook_id, owner = True ):
216
-      notebook.owner = False
217 211
 
218 212
     if note_id:
219 213
       note = self.__database.load( Note, note_id, revision )
@@ -234,7 +228,7 @@ class Notebooks( object ):
234 228
     startup_notes = self.__database.select_many( Note, notebook.sql_load_startup_notes() )
235 229
     total_notes_count = self.__database.select_one( int, notebook.sql_count_notes(), use_cache = True )
236 230
 
237
-    if self.__users.check_access( user_id, notebook_id, owner = True ):
231
+    if self.__users.load_notebook( user_id, notebook_id, owner = True ):
238 232
       invites = self.__database.select_many( Invite, Invite.sql_load_notebook_invites( notebook_id ) )
239 233
     else:
240 234
       invites = []
@@ -350,7 +344,9 @@ class Notebooks( object ):
350 344
     @raise Access_error: the current user doesn't have access to the given notebook or note
351 345
     @raise Validation_error: one of the arguments is invalid
352 346
     """
353
-    if not self.__users.check_access( user_id, notebook_id ):
347
+    notebook = self.__users.load_notebook( user_id, notebook_id )
348
+
349
+    if not notebook:
354 350
       raise Access_error()
355 351
 
356 352
     note = self.__database.load( Note, note_id, revision )
@@ -362,8 +358,7 @@ class Notebooks( object ):
362 358
       )
363 359
 
364 360
     if note and note.notebook_id != notebook_id:
365
-      notebook = self.__database.load( Notebook, notebook_id )
366
-      if notebook and note.notebook_id == notebook.trash_id:
361
+      if note.notebook_id == notebook.trash_id:
367 362
         if revision:
368 363
           return dict(
369 364
             note = summarize and self.summarize_note( note ) or note,
@@ -414,15 +409,12 @@ class Notebooks( object ):
414 409
     @raise Access_error: the current user doesn't have access to the given notebook
415 410
     @raise Validation_error: one of the arguments is invalid
416 411
     """
417
-    if not self.__users.check_access( user_id, notebook_id ):
418
-      raise Access_error()
412
+    notebook = self.__users.load_notebook( user_id, notebook_id )
419 413
 
420
-    notebook = self.__database.load( Notebook, notebook_id )
414
+    if not notebook:
415
+      raise Access_error()
421 416
 
422
-    if notebook is None:
423
-      note = None
424
-    else:
425
-      note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
417
+    note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
426 418
 
427 419
     return dict(
428 420
       note = summarize and self.summarize_note( note ) or note,
@@ -520,15 +512,12 @@ class Notebooks( object ):
520 512
     @raise Access_error: the current user doesn't have access to the given notebook
521 513
     @raise Validation_error: one of the arguments is invalid
522 514
     """
523
-    if not self.__users.check_access( user_id, notebook_id ):
524
-      raise Access_error()
515
+    notebook = self.__users.load_notebook( user_id, notebook_id )
525 516
 
526
-    notebook = self.__database.load( Notebook, notebook_id )
517
+    if not notebook:
518
+      raise Access_error()
527 519
 
528
-    if notebook is None:
529
-      note = None
530
-    else:
531
-      note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
520
+    note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
532 521
 
533 522
     return dict(
534 523
       note_id = note and note.object_id or None,
@@ -558,7 +547,9 @@ class Notebooks( object ):
558 547
     @raise Access_error: the current user doesn't have access to the given notebook or note
559 548
     @raise Validation_error: one of the arguments is invalid
560 549
     """
561
-    if not self.__users.check_access( user_id, notebook_id ):
550
+    notebook = self.__users.load_notebook( user_id, notebook_id )
551
+
552
+    if not notebook:
562 553
       raise Access_error()
563 554
 
564 555
     note = self.__database.load( Note, note_id )
@@ -570,8 +561,7 @@ class Notebooks( object ):
570 561
         )
571 562
 
572 563
       if note.notebook_id != notebook_id:
573
-        notebook = self.__database.load( Notebook, notebook_id )
574
-        if notebook and note.notebook_id == notebook.trash_id:
564
+        if note.notebook_id == notebook.trash_id:
575 565
           return dict(
576 566
             revisions = None,
577 567
           )
@@ -610,10 +600,8 @@ class Notebooks( object ):
610 600
     @raise Access_error: the current user doesn't have access to the given notebook or note
611 601
     @raise Validation_error: one of the arguments is invalid
612 602
     """
613
-    if not self.__users.check_access( user_id, notebook_id ):
614
-      raise Access_error()
603
+    notebook = self.__users.load_notebook( user_id, notebook_id )
615 604
 
616
-    notebook = self.__database.load( Notebook, notebook_id )
617 605
     if not notebook:
618 606
       raise Access_error()
619 607
 
@@ -696,17 +684,19 @@ class Notebooks( object ):
696 684
     @raise Access_error: the current user doesn't have access to the given notebook
697 685
     @raise Validation_error: one of the arguments is invalid
698 686
     """
699
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
700
-      raise Access_error()
701
-
687
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id )
702 688
     user = self.__database.load( User, user_id )
703
-    notebook = self.__database.load( Notebook, notebook_id )
704 689
 
705 690
     if not user or not notebook:
706
-      raise Access_error()
691
+      raise Access_error();
707 692
 
708 693
     note = self.__database.load( Note, note_id )
709 694
 
695
+    # if the user has read-write access only to their own notes in this notebook, force the startup
696
+    # flag to be True for this note
697
+    if notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES:
698
+      startup = True
699
+
710 700
     # check whether the provided note contents have been changed since the previous revision
711 701
     def update_note( current_notebook, old_note, startup, user ):
712 702
       # the note hasn't been changed, so bail without updating it
@@ -805,11 +795,8 @@ class Notebooks( object ):
805 795
     @raise Access_error: the current user doesn't have access to the given notebook
806 796
     @raise Validation_error: one of the arguments is invalid
807 797
     """
808
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
809
-      raise Access_error()
810
-
798
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True )
811 799
     user = self.__database.load( User, user_id )
812
-    notebook = self.__database.load( Notebook, notebook_id )
813 800
 
814 801
     if not user or not notebook:
815 802
       raise Access_error()
@@ -819,6 +806,9 @@ class Notebooks( object ):
819 806
     if not note:
820 807
       raise Access_error()
821 808
 
809
+    if not self.__users.load_notebook( user_id, note.notebook_id, read_write = True, note_id = note.object_id ):
810
+      raise Access_error()
811
+
822 812
     # check whether the provided note contents have been changed since the previous revision
823 813
     def update_note( current_notebook, old_note, user ):
824 814
       # if the revision to revert to is already the newest revision, bail without updating the note
@@ -895,10 +885,7 @@ class Notebooks( object ):
895 885
     @raise Access_error: the current user doesn't have access to the given notebook
896 886
     @raise Validation_error: one of the arguments is invalid
897 887
     """
898
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
899
-      raise Access_error()
900
-
901
-    notebook = self.__database.load( Notebook, notebook_id )
888
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id )
902 889
 
903 890
     if not notebook:
904 891
       raise Access_error()
@@ -949,10 +936,7 @@ class Notebooks( object ):
949 936
     @raise Access_error: the current user doesn't have access to the given notebook
950 937
     @raise Validation_error: one of the arguments is invalid
951 938
     """
952
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
953
-      raise Access_error()
954
-
955
-    notebook = self.__database.load( Notebook, notebook_id )
939
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id )
956 940
 
957 941
     if not notebook:
958 942
       raise Access_error()
@@ -1005,12 +989,9 @@ class Notebooks( object ):
1005 989
     @raise Access_error: the current user doesn't have access to the given notebook
1006 990
     @raise Validation_error: one of the arguments is invalid
1007 991
     """
1008
-    if not self.__users.check_access( user_id, notebook_id, read_write = True ):
1009
-      raise Access_error()
1010
-
1011
-    notebook = self.__database.load( Notebook, notebook_id )
992
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True )
1012 993
 
1013
-    if not notebook:
994
+    if not notebook or notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES:
1014 995
       raise Access_error()
1015 996
 
1016 997
     notes = self.__database.select_many( Note, notebook.sql_load_notes() )
@@ -1063,7 +1044,9 @@ class Notebooks( object ):
1063 1044
     @raise Validation_error: one of the arguments is invalid
1064 1045
     @raise Search_error: the provided search_text is invalid
1065 1046
     """
1066
-    if not self.__users.check_access( user_id, notebook_id ):
1047
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1048
+
1049
+    if not notebook:
1067 1050
       raise Access_error()
1068 1051
 
1069 1052
     MAX_SEARCH_TEXT_LENGTH = 256
@@ -1112,14 +1095,19 @@ class Notebooks( object ):
1112 1095
     @raise Validation_error: one of the arguments is invalid
1113 1096
     @raise Search_error: the provided search_text is invalid
1114 1097
     """
1115
-    if not self.__users.check_access( user_id, notebook_id ):
1116
-      raise Access_error()
1117
-
1118 1098
     # if the anonymous user has access to the given notebook, then run the search as the anonymous
1119 1099
     # user instead of the given user id
1120
-    if self.__users.check_access( user_id = None, notebook_id = notebook_id ) is True:
1121
-      anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
1100
+    anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
1101
+    if not anonymous:
1102
+      raise Access_error()
1103
+
1104
+    notebook = self.__users.load_notebook( anonymous.object_id, notebook_id )
1105
+    if notebook:
1122 1106
       user_id = anonymous.object_id
1107
+    else:
1108
+      notebook = self.__users.load_notebook( user_id, notebook_id )
1109
+      if not notebook:
1110
+        raise Access_error()
1123 1111
 
1124 1112
     MAX_SEARCH_TEXT_LENGTH = 256
1125 1113
     if len( search_text ) > MAX_SEARCH_TEXT_LENGTH:
@@ -1162,10 +1150,7 @@ class Notebooks( object ):
1162 1150
     @raise Access_error: the current user doesn't have access to the given notebook
1163 1151
     @raise Validation_error: one of the arguments is invalid
1164 1152
     """
1165
-    if not self.__users.check_access( user_id, notebook_id ):
1166
-      raise Access_error()
1167
-
1168
-    notebook = self.__database.load( Notebook, notebook_id )
1153
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1169 1154
 
1170 1155
     if not notebook:
1171 1156
       raise Access_error()
@@ -1197,10 +1182,7 @@ class Notebooks( object ):
1197 1182
     @raise Access_error: the current user doesn't have access to the given notebook
1198 1183
     @raise Validation_error: one of the arguments is invalid
1199 1184
     """
1200
-    if not self.__users.check_access( user_id, notebook_id ):
1201
-      raise Access_error()
1202
-
1203
-    notebook = self.__database.load( Notebook, notebook_id )
1185
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1204 1186
 
1205 1187
     if not notebook:
1206 1188
       raise Access_error()
@@ -1234,10 +1216,7 @@ class Notebooks( object ):
1234 1216
     @raise Access_error: the current user doesn't have access to the given notebook
1235 1217
     @raise Validation_error: one of the arguments is invalid
1236 1218
     """
1237
-    if not self.__users.check_access( user_id, notebook_id ):
1238
-      raise Access_error()
1239
-
1240
-    notebook = self.__database.load( Notebook, notebook_id )
1219
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1241 1220
 
1242 1221
     if not notebook:
1243 1222
       raise Access_error()
@@ -1346,13 +1325,10 @@ class Notebooks( object ):
1346 1325
     @raise Access_error: the current user doesn't have access to the given notebook
1347 1326
     @raise Validation_error: one of the arguments is invalid
1348 1327
     """
1328
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
1349 1329
     user = self.__database.load( User, user_id )
1350
-    if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
1351
-      raise Access_error()
1352 1330
 
1353
-    notebook = self.__database.load( Notebook, notebook_id )
1354
-
1355
-    if not notebook:
1331
+    if not user or not notebook:
1356 1332
       raise Access_error()
1357 1333
 
1358 1334
     # prevent renaming of the trash notebook to anything
@@ -1399,14 +1375,10 @@ class Notebooks( object ):
1399 1375
     if user_id is None:
1400 1376
       raise Access_error()
1401 1377
 
1378
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
1402 1379
     user = self.__database.load( User, user_id )
1403 1380
 
1404
-    if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
1405
-      raise Access_error()
1406
-
1407
-    notebook = self.__database.load( Notebook, notebook_id )
1408
-
1409
-    if not notebook:
1381
+    if not user or not notebook:
1410 1382
       raise Access_error()
1411 1383
 
1412 1384
     # prevent deletion of a trash notebook directly
@@ -1454,14 +1426,10 @@ class Notebooks( object ):
1454 1426
     if user_id is None:
1455 1427
       raise Access_error()
1456 1428
 
1429
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
1457 1430
     user = self.__database.load( User, user_id )
1458 1431
 
1459
-    if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
1460
-      raise Access_error()
1461
-
1462
-    notebook = self.__database.load( Notebook, notebook_id )
1463
-
1464
-    if not notebook:
1432
+    if not user or not notebook:
1465 1433
       raise Access_error()
1466 1434
 
1467 1435
     # prevent deletion of a trash notebook directly
@@ -1498,10 +1466,7 @@ class Notebooks( object ):
1498 1466
     if user_id is None:
1499 1467
       raise Access_error()
1500 1468
 
1501
-    if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
1502
-      raise Access_error()
1503
-
1504
-    notebook = self.__database.load( Notebook, notebook_id )
1469
+    notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
1505 1470
 
1506 1471
     if not notebook:
1507 1472
       raise Access_error()
@@ -1537,11 +1502,10 @@ class Notebooks( object ):
1537 1502
     @raise Access_error: the current user doesn't have access to the given notebook
1538 1503
     @raise Validation_error: one of the arguments is invalid
1539 1504
     """
1540
-    if not self.__users.check_access( user_id, notebook_id ):
1541
-      raise Access_error()
1542
-
1505
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1543 1506
     user = self.__database.load( User, user_id )
1544
-    if not user:
1507
+
1508
+    if not user or not notebook:
1545 1509
       raise Access_error()
1546 1510
 
1547 1511
     # load the notebooks to which this user has access
@@ -1609,11 +1573,10 @@ class Notebooks( object ):
1609 1573
     @raise Access_error: the current user doesn't have access to the given notebook
1610 1574
     @raise Validation_error: one of the arguments is invalid
1611 1575
     """
1612
-    if not self.__users.check_access( user_id, notebook_id ):
1613
-      raise Access_error()
1614
-
1576
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1615 1577
     user = self.__database.load( User, user_id )
1616
-    if not user:
1578
+
1579
+    if not user or not notebook:
1617 1580
       raise Access_error()
1618 1581
 
1619 1582
     # load the notebooks to which this user has access
@@ -1676,7 +1639,6 @@ class Notebooks( object ):
1676 1639
     Provide the information necessary to display a notebook's recent updated/created notes, in
1677 1640
     reverse chronological order by update time.
1678 1641
     
1679
-
1680 1642
     @type notebook_id: unicode
1681 1643
     @param notebook_id: id of the notebook containing the notes
1682 1644
     @type start: unicode or NoneType
@@ -1689,10 +1651,7 @@ class Notebooks( object ):
1689 1651
     @return: { 'notes': recent_notes_list }
1690 1652
     @raise Access_error: the current user doesn't have access to the given notebook or note
1691 1653
     """
1692
-    if not self.__users.check_access( user_id, notebook_id ):
1693
-      raise Access_error()
1694
-    
1695
-    notebook = self.__database.load( Notebook, notebook_id )
1654
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1696 1655
 
1697 1656
     if notebook is None:
1698 1657
       raise Access_error()
@@ -1720,10 +1679,7 @@ class Notebooks( object ):
1720 1679
     @return: data for Main_page() constructor
1721 1680
     @raise Access_error: the current user doesn't have access to the given notebook or note
1722 1681
     """
1723
-    if not self.__users.check_access( user_id, notebook_id ):
1724
-      raise Access_error()
1725
-    
1726
-    notebook = self.__database.load( Notebook, notebook_id )
1682
+    notebook = self.__users.load_notebook( user_id, notebook_id )
1727 1683
 
1728 1684
     if notebook is None:
1729 1685
       raise Access_error()
@@ -1798,7 +1754,7 @@ class Notebooks( object ):
1798 1754
       raise Access_error()
1799 1755
 
1800 1756
     db_file = self.__database.load( File, file_id )
1801
-    if db_file is None or not self.__users.check_access( user_id, db_file.notebook_id ):
1757
+    if db_file is None or not self.__users.load_notebook( user_id, db_file.notebook_id ):
1802 1758
       raise Access_error()
1803 1759
 
1804 1760
     # if the file has a "note_id" header column, record its index

+ 2
- 2
controller/Root.py View File

@@ -58,7 +58,7 @@ class Root( object ):
58 58
       settings[ u"global" ].get( u"luminotes.download_products", [] ),
59 59
     )
60 60
     self.__notebooks = Notebooks( database, self.__users, self.__files, settings[ u"global" ].get( u"luminotes.https_url", u"" ) )
61
-    self.__forums = Forums( database, self.__users )
61
+    self.__forums = Forums( database, self.__notebooks, self.__users )
62 62
     self.__suppress_exceptions = suppress_exceptions # used for unit tests
63 63
 
64 64
   @expose( Main_page )
@@ -486,4 +486,4 @@ class Root( object ):
486 486
   users = property( lambda self: self.__users )
487 487
   groups = property( lambda self: self.__groups )
488 488
   files = property( lambda self: self.__files )
489
-#  forums = property( lambda self: self.__forums )
489
+  forums = property( lambda self: self.__forums )

+ 49
- 22
controller/Users.py View File

@@ -702,34 +702,62 @@ class Users( object ):
702 702
 
703 703
     return user
704 704
 
705
-  def check_access( self, user_id, notebook_id, read_write = False, owner = False ):
705
+  def load_notebook( self, user_id, notebook_id, read_write = False, owner = False, note_id = None ):
706 706
     """
707
-    Determine whether the given user has access to the given notebook.
707
+    Determine whether the given user has access to the given notebook, and if so, return that
708
+    notebook.
709
+
710
+    If the notebook.read_write member is READ_WRITE_FOR_OWN_NOTES, and a particular note_id is
711
+    given, then make sure that the given note_id is one of the user's own notes.
708 712
 
709 713
     @type user_id: unicode
710 714
     @param user_id: id of user whose access to check
711 715
     @type notebook_id: unicode
712 716
     @param notebook_id: id of notebook to check access for
713
-    @type read_write: bool
714
-    @param read_write: True if read-write access is being checked, False if read-only access (defaults to False)
717
+    @type read_write: boolean
718
+    @param read_write: True if the notebook must be READ_WRITE or READ_WRITE_FOR_OWN_NOTES,
719
+                       False if read-write access is not to be checked (defaults to False)
715 720
     @type owner: bool
716 721
     @param owner: True if owner-level access is being checked (defaults to False)
717
-    @rtype: bool
718
-    @return: True if the user has access
722
+    @type note_id: unicode
723
+    @param note_id: id of the note in the given notebook that the user is trying to access.
724
+                    if the notebook is READ_WRITE_FOR_OWN_NOTES, then the given note is checked
725
+                    to make sure its user_id is the same as the given user_id. for READ_WRITE
726
+                    and READ_ONLY notebooks, this note_id parameter is ignored
727
+    @rtype: Notebook or NoneType
728
+    @return: the loaded notebook if the user has access to it, None otherwise
719 729
     """
720 730
     anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
731
+    notebook = self.__database.select_one( Notebook, anonymous.sql_load_notebooks( notebook_id = notebook_id ) )
721 732
 
722
-    if self.__database.select_one( bool, anonymous.sql_has_access( notebook_id, read_write, owner ) ):
723
-      return True
724
-
725
-    if user_id:
726
-      # check if the given user has access to this notebook
733
+    if not notebook and user_id:
727 734
       user = self.__database.load( User, user_id )
735
+      if not user:
736
+        return None
728 737
 
729
-      if user and self.__database.select_one( bool, user.sql_has_access( notebook_id, read_write, owner ) ):
730
-        return True
738
+      notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( notebook_id = notebook_id ) )
731 739
 
732
-    return False
740
+    # if the user has no access to this notebook, bail
741
+    if notebook is None:
742
+      return None
743
+
744
+    if read_write and notebook.read_write == Notebook.READ_ONLY:
745
+      return None
746
+
747
+    if owner and not notebook.owner:
748
+      return None
749
+
750
+    # if a particular note_id is given, and the notebook is READ_WRITE_FOR_OWN_NOTES, then check
751
+    # that the user is associated with that note
752
+    if note_id and notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES:
753
+      note = self.__database.load( Note, note_id )
754
+      if not note:
755
+        return None
756
+
757
+      if user_id != note.user_id or notebook_id != note.notebook_id:
758
+        return None
759
+        
760
+    return notebook
733 761
 
734 762
   def check_group( self, user_id, group_id, admin = False ):
735 763
     """
@@ -1006,7 +1034,9 @@ class Users( object ):
1006 1034
     if len( email_addresses ) > 5000:
1007 1035
       raise Invite_error( u"Please enter fewer email addresses." )
1008 1036
 
1009
-    if not self.check_access( user_id, notebook_id, read_write = True, owner = True ):
1037
+    notebook = self.load_notebook( user_id, notebook_id, read_write = True, owner = True )
1038
+
1039
+    if not notebook:
1010 1040
       raise Access_error()
1011 1041
 
1012 1042
     # except for viewer-only invites, this feature requires a rate plan above basic
@@ -1027,10 +1057,6 @@ class Users( object ):
1027 1057
     else:
1028 1058
       raise Access_error()
1029 1059
 
1030
-    notebook = self.__database.load( Notebook, notebook_id )
1031
-    if notebook is None:
1032
-      raise Access_error()
1033
-
1034 1060
     # parse email_addresses string into individual email addresses
1035 1061
     email_addresses_list = set()
1036 1062
     for piece in WHITESPACE_OR_COMMA_PATTERN.split( email_addresses ):
@@ -1136,12 +1162,13 @@ class Users( object ):
1136 1162
     @raise Validation_error: one of the arguments is invalid
1137 1163
     @raise Access_error: user_id doesn't have owner-level notebook access to revoke an invite
1138 1164
     """
1139
-    if not self.check_access( user_id, notebook_id, read_write = True, owner = True ):
1165
+    notebook = self.load_notebook( user_id, notebook_id, read_write = True, owner = True )
1166
+
1167
+    if not notebook:
1140 1168
       raise Access_error()
1141 1169
 
1142 1170
     invite = self.__database.load( Invite, invite_id )
1143
-    notebook = self.__database.load( Notebook, notebook_id )
1144
-    if not notebook or not invite or not invite.email_address or invite.notebook_id != notebook_id:
1171
+    if not invite or not invite.email_address or invite.notebook_id != notebook_id:
1145 1172
       raise Access_error()
1146 1173
 
1147 1174
     self.__database.execute(

+ 1
- 1
controller/test/Test_database.py View File

@@ -10,7 +10,7 @@ from controller.Database import Database, Connection_wrapper
10 10
 
11 11
 class Test_database( object ):
12 12
   def setUp( self ):
13
-    # make an in-memory sqlite database to use in place of PostgreSQL during testing
13
+    # make an in-memory sqlite database to use during testing
14 14
     self.connection = Connection_wrapper( sqlite.connect( ":memory:", detect_types = sqlite.PARSE_DECLTYPES, check_same_thread = False ) )
15 15
     self.cache = Stub_cache()
16 16
     cursor = self.connection.cursor()

+ 131
- 40
controller/test/Test_notebooks.py View File

@@ -175,13 +175,13 @@ class Test_notebooks( Test_controller ):
175 175
     assert result.get( u"user" ).object_id == self.user.object_id
176 176
     assert len( result.get( u"notebooks" ) ) == 3
177 177
     assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
178
-    assert result.get( u"notebooks" )[ 2 ].read_write == True
178
+    assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
179 179
     assert result.get( u"notebooks" )[ 2 ].owner == True
180 180
     assert result.get( u"login_url" ) is None
181 181
     assert result.get( u"logout_url" )
182 182
     assert result.get( u"rate_plan" )
183 183
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
184
-    assert result.get( u"notebook" ).read_write == True
184
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
185 185
     assert result.get( u"notebook" ).owner == True
186 186
     assert len( result.get( u"startup_notes" ) ) == 1
187 187
     assert result[ "total_notes_count" ] == 2
@@ -211,13 +211,13 @@ class Test_notebooks( Test_controller ):
211 211
     assert result.get( u"user" ).object_id == self.user.object_id
212 212
     assert len( result.get( u"notebooks" ) ) == 1
213 213
     assert result.get( u"notebooks" )[ 0 ].object_id == self.notebook.object_id
214
-    assert result.get( u"notebooks" )[ 0 ].read_write == False
214
+    assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_ONLY
215 215
     assert result.get( u"notebooks" )[ 0 ].owner == False
216 216
     assert result.get( u"login_url" ) is None
217 217
     assert result.get( u"logout_url" )
218 218
     assert result.get( u"rate_plan" )
219 219
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
220
-    assert result.get( u"notebook" ).read_write == False
220
+    assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
221 221
     assert result.get( u"notebook" ).owner == False
222 222
     assert len( result.get( u"startup_notes" ) ) == 1
223 223
     assert result[ "total_notes_count" ] == 2
@@ -247,13 +247,13 @@ class Test_notebooks( Test_controller ):
247 247
     assert result.get( u"user" ).object_id == self.user.object_id
248 248
     assert len( result.get( u"notebooks" ) ) == 1
249 249
     assert result.get( u"notebooks" )[ 0 ].object_id == self.notebook.object_id
250
-    assert result.get( u"notebooks" )[ 0 ].read_write == True
250
+    assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_WRITE
251 251
     assert result.get( u"notebooks" )[ 0 ].owner == False
252 252
     assert result.get( u"login_url" ) is None
253 253
     assert result.get( u"logout_url" )
254 254
     assert result.get( u"rate_plan" )
255 255
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
256
-    assert result.get( u"notebook" ).read_write == True
256
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
257 257
     assert result.get( u"notebook" ).owner == False
258 258
     assert len( result.get( u"startup_notes" ) ) == 1
259 259
     assert result[ "total_notes_count" ] == 2
@@ -283,13 +283,13 @@ class Test_notebooks( Test_controller ):
283 283
     assert result.get( u"user" ).object_id == self.user.object_id
284 284
     assert len( result.get( u"notebooks" ) ) == 3
285 285
     assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
286
-    assert result.get( u"notebooks" )[ 2 ].read_write == True
286
+    assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
287 287
     assert result.get( u"notebooks" )[ 2 ].owner == True
288 288
     assert result.get( u"login_url" ) is None
289 289
     assert result.get( u"logout_url" )
290 290
     assert result.get( u"rate_plan" )
291 291
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
292
-    assert result.get( u"notebook" ).read_write == True
292
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
293 293
     assert result.get( u"notebook" ).owner == True
294 294
     assert len( result.get( u"startup_notes" ) ) == 1
295 295
     assert result[ "total_notes_count" ] == 2
@@ -319,13 +319,13 @@ class Test_notebooks( Test_controller ):
319 319
     assert result.get( u"user" ).object_id == self.user.object_id
320 320
     assert len( result.get( u"notebooks" ) ) == 1
321 321
     assert result.get( u"notebooks" )[ 0 ].object_id == self.anon_notebook.object_id
322
-    assert result.get( u"notebooks" )[ 0 ].read_write == False
322
+    assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_ONLY
323 323
     assert result.get( u"notebooks" )[ 0 ].owner == False
324 324
     assert result.get( u"login_url" ) is None
325 325
     assert result.get( u"logout_url" )
326 326
     assert result.get( u"rate_plan" )
327 327
     assert result.get( u"notebook" ).object_id == self.anon_notebook.object_id
328
-    assert result.get( u"notebook" ).read_write == False
328
+    assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
329 329
     assert result.get( u"notebook" ).owner == False
330 330
     assert len( result.get( u"startup_notes" ) ) == 0
331 331
     assert result[ "total_notes_count" ] == 0
@@ -351,13 +351,13 @@ class Test_notebooks( Test_controller ):
351 351
     assert result.get( u"user" ).object_id == self.user.object_id
352 352
     assert len( result.get( u"notebooks" ) ) == 1
353 353
     assert result.get( u"notebooks" )[ 0 ].object_id == self.anon_notebook.object_id
354
-    assert result.get( u"notebooks" )[ 0 ].read_write == False
354
+    assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_ONLY
355 355
     assert result.get( u"notebooks" )[ 0 ].owner == False
356 356
     assert result.get( u"login_url" ) is None
357 357
     assert result.get( u"logout_url" )
358 358
     assert result.get( u"rate_plan" )
359 359
     assert result.get( u"notebook" ).object_id == self.anon_notebook.object_id
360
-    assert result.get( u"notebook" ).read_write == False
360
+    assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
361 361
     assert result.get( u"notebook" ).owner == False
362 362
     assert len( result.get( u"startup_notes" ) ) == 0
363 363
     assert result[ "total_notes_count" ] == 0
@@ -380,14 +380,18 @@ class Test_notebooks( Test_controller ):
380 380
     
381 381
     assert result.get( u"user" ).object_id == self.user.object_id
382 382
     assert len( result.get( u"notebooks" ) ) == 3
383
-    assert result.get( u"notebooks" )[ 1 ].object_id == self.anon_notebook.object_id
384
-    assert result.get( u"notebooks" )[ 1 ].read_write == False
385
-    assert result.get( u"notebooks" )[ 1 ].owner == False
383
+    notebook = result[ u"notebooks" ][ 0 ]
384
+    if notebook.name == u"trash":
385
+      notebook = result[ u"notebooks" ][ 1 ]
386
+
387
+    assert notebook.object_id == self.anon_notebook.object_id
388
+    assert notebook.read_write == Notebook.READ_ONLY
389
+    assert notebook.owner == False
386 390
     assert result.get( u"login_url" ) is None
387 391
     assert result.get( u"logout_url" )
388 392
     assert result.get( u"rate_plan" )
389 393
     assert result.get( u"notebook" ).object_id == self.anon_notebook.object_id
390
-    assert result.get( u"notebook" ).read_write == False
394
+    assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
391 395
     assert result.get( u"notebook" ).owner == False
392 396
     assert len( result.get( u"startup_notes" ) ) == 0
393 397
     assert result[ "total_notes_count" ] == 0
@@ -468,13 +472,13 @@ class Test_notebooks( Test_controller ):
468 472
     assert result.get( u"user" ).object_id == self.user.object_id
469 473
     assert len( result.get( u"notebooks" ) ) == 3
470 474
     assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
471
-    assert result.get( u"notebooks" )[ 2 ].read_write == True
475
+    assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
472 476
     assert result.get( u"notebooks" )[ 2 ].owner == True
473 477
     assert result.get( u"login_url" ) is None
474 478
     assert result.get( u"logout_url" )
475 479
     assert result.get( u"rate_plan" )
476 480
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
477
-    assert result.get( u"notebook" ).read_write == True
481
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
478 482
     assert result.get( u"notebook" ).owner == True
479 483
     assert len( result.get( u"startup_notes" ) ) == 1
480 484
     assert result[ "total_notes_count" ] == 2
@@ -511,13 +515,13 @@ class Test_notebooks( Test_controller ):
511 515
     assert result.get( u"user" ).object_id == self.user.object_id
512 516
     assert len( result.get( u"notebooks" ) ) == 3
513 517
     assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
514
-    assert result.get( u"notebooks" )[ 2 ].read_write == True
518
+    assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
515 519
     assert result.get( u"notebooks" )[ 2 ].owner == True
516 520
     assert result.get( u"login_url" ) is None
517 521
     assert result.get( u"logout_url" )
518 522
     assert result.get( u"rate_plan" )
519 523
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
520
-    assert result.get( u"notebook" ).read_write == True
524
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
521 525
     assert result.get( u"notebook" ).owner == True
522 526
     assert len( result.get( u"startup_notes" ) ) == 1
523 527
     assert result[ "total_notes_count" ] == 2
@@ -560,13 +564,13 @@ class Test_notebooks( Test_controller ):
560 564
     assert result.get( u"user" ).object_id == self.user.object_id
561 565
     assert len( result.get( u"notebooks" ) ) == 3
562 566
     assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
563
-    assert result.get( u"notebooks" )[ 2 ].read_write == True
567
+    assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
564 568
     assert result.get( u"notebooks" )[ 2 ].owner == True
565 569
     assert result.get( u"login_url" ) is None
566 570
     assert result.get( u"logout_url" )
567 571
     assert result.get( u"rate_plan" )
568 572
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
569
-    assert result.get( u"notebook" ).read_write == True
573
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
570 574
     assert result.get( u"notebook" ).owner == True
571 575
     assert len( result.get( u"startup_notes" ) ) == 1
572 576
     assert result[ "total_notes_count" ] == 2
@@ -602,13 +606,13 @@ class Test_notebooks( Test_controller ):
602 606
     assert result.get( u"user" ).object_id == self.user.object_id
603 607
     assert len( result.get( u"notebooks" ) ) == 3
604 608
     assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
605
-    assert result.get( u"notebooks" )[ 2 ].read_write == True
609
+    assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
606 610
     assert result.get( u"notebooks" )[ 2 ].owner == True
607 611
     assert result.get( u"login_url" ) is None
608 612
     assert result.get( u"logout_url" )
609 613
     assert result.get( u"rate_plan" )
610 614
     assert result.get( u"notebook" ).object_id == self.notebook.object_id
611
-    assert result.get( u"notebook" ).read_write == True
615
+    assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
612 616
     assert result.get( u"notebook" ).owner == True
613 617
     assert len( result.get( u"startup_notes" ) ) == 1
614 618
     assert result[ "total_notes_count" ] == 2
@@ -644,7 +648,7 @@ class Test_notebooks( Test_controller ):
644 648
     assert invite.object_id == self.invite.object_id
645 649
 
646 650
     assert notebook.object_id == self.notebook.object_id
647
-    assert notebook.read_write == True
651
+    assert notebook.read_write == Notebook.READ_WRITE
648 652
     assert notebook.owner == True
649 653
     assert len( startup_notes ) == 1
650 654
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -669,7 +673,7 @@ class Test_notebooks( Test_controller ):
669 673
     assert invite.object_id == self.invite.object_id
670 674
 
671 675
     assert notebook.object_id == self.notebook.object_id
672
-    assert notebook.read_write == False
676
+    assert notebook.read_write == Notebook.READ_ONLY
673 677
     assert notebook.owner == True
674 678
     assert len( startup_notes ) == 1
675 679
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -694,7 +698,7 @@ class Test_notebooks( Test_controller ):
694 698
     assert invite.object_id == self.invite.object_id
695 699
 
696 700
     assert notebook.object_id == self.notebook.object_id
697
-    assert notebook.read_write == True
701
+    assert notebook.read_write == Notebook.READ_WRITE
698 702
     assert notebook.owner == False
699 703
     assert len( startup_notes ) == 1
700 704
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -718,7 +722,7 @@ class Test_notebooks( Test_controller ):
718 722
     assert invite.object_id == self.invite.object_id
719 723
 
720 724
     assert notebook.object_id == self.notebook.object_id
721
-    assert notebook.read_write == True
725
+    assert notebook.read_write == Notebook.READ_WRITE
722 726
     assert notebook.owner == True
723 727
     assert len( startup_notes ) == 1
724 728
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -751,7 +755,7 @@ class Test_notebooks( Test_controller ):
751 755
     assert invite.object_id == self.invite.object_id
752 756
 
753 757
     assert notebook.object_id == self.notebook.object_id
754
-    assert notebook.read_write == True
758
+    assert notebook.read_write == Notebook.READ_WRITE
755 759
     assert notebook.owner == True
756 760
     assert len( startup_notes ) == 1
757 761
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -790,7 +794,7 @@ class Test_notebooks( Test_controller ):
790 794
     assert invite.object_id == self.invite.object_id
791 795
 
792 796
     assert notebook.object_id == self.notebook.object_id
793
-    assert notebook.read_write == True
797
+    assert notebook.read_write == Notebook.READ_WRITE
794 798
     assert notebook.owner == True
795 799
     assert len( startup_notes ) == 1
796 800
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -830,7 +834,7 @@ class Test_notebooks( Test_controller ):
830 834
     assert invites[ 1 ].object_id == invite.object_id
831 835
 
832 836
     assert notebook.object_id == self.notebook.object_id
833
-    assert notebook.read_write == True
837
+    assert notebook.read_write == Notebook.READ_WRITE
834 838
     assert notebook.owner == True
835 839
     assert len( startup_notes ) == 1
836 840
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -861,7 +865,7 @@ class Test_notebooks( Test_controller ):
861 865
     assert invites[ 1 ].object_id == invite.object_id
862 866
 
863 867
     assert notebook.object_id == self.notebook.object_id
864
-    assert notebook.read_write == True
868
+    assert notebook.read_write == Notebook.READ_WRITE
865 869
     assert notebook.owner == True
866 870
     assert len( startup_notes ) == 1
867 871
     assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -901,7 +905,7 @@ class Test_notebooks( Test_controller ):
901 905
     assert result[ "invites" ] == []
902 906
 
903 907
     assert notebook.object_id == self.anon_notebook.object_id
904
-    assert notebook.read_write == False
908
+    assert notebook.read_write == Notebook.READ_ONLY
905 909
     assert notebook.owner == False
906 910
     assert len( startup_notes ) == 0
907 911
     user = self.database.load( User, self.user.object_id )
@@ -1727,6 +1731,72 @@ class Test_notebooks( Test_controller ):
1727 1731
   def test_save_startup_note( self ):
1728 1732
     self.test_save_note( startup = True )
1729 1733
 
1734
+  def test_save_note_in_notebook_with_read_write_for_own_notes( self ):
1735
+    self.login()
1736
+
1737
+    self.database.execute( self.user.sql_update_access( 
1738
+      self.notebook.object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = True,
1739
+    ) )
1740
+
1741
+    previous_revision = self.note.revision
1742
+    new_note_contents = u"<h3>new title</h3>new blah"
1743
+    result = self.http_post( "/notebooks/save_note/", dict(
1744
+      notebook_id = self.notebook.object_id,
1745
+      note_id = self.note.object_id,
1746
+      contents = new_note_contents,
1747
+      startup = False,
1748
+      previous_revision = previous_revision,
1749
+    ), session_id = self.session_id )
1750
+
1751
+    assert result[ "new_revision" ]
1752
+    assert result[ "new_revision" ].revision != previous_revision
1753
+    assert result[ "new_revision" ].user_id == self.user.object_id
1754
+    assert result[ "new_revision" ].username == self.username
1755
+    current_revision = result[ "new_revision" ].revision
1756
+    assert result[ "previous_revision" ].revision == previous_revision
1757
+    assert result[ "previous_revision" ].user_id == self.user.object_id
1758
+    assert result[ "previous_revision" ].username == self.username
1759
+
1760
+    # make sure the old title can no longer be loaded
1761
+    result = self.http_post( "/notebooks/load_note_by_title/", dict(
1762
+      notebook_id = self.notebook.object_id,
1763
+      note_title = "my title",
1764
+    ), session_id = self.session_id )
1765
+
1766
+    note = result[ "note" ]
1767
+    assert note == None
1768
+
1769
+    # make sure the new title is now loadable
1770
+    result = self.http_post( "/notebooks/load_note_by_title/", dict(
1771
+      notebook_id = self.notebook.object_id,
1772
+      note_title = "new title",
1773
+    ), session_id = self.session_id )
1774
+
1775
+    note = result[ "note" ]
1776
+
1777
+    assert note.object_id == self.note.object_id
1778
+    assert note.title == "new title"
1779
+    assert note.contents == new_note_contents
1780
+    assert note.startup == True # startup is forced to True in READ_WRITE_FOR_OWN_NOTES notebook
1781
+    assert note.user_id == self.user.object_id
1782
+    assert note.rank == 0
1783
+
1784
+    # make sure that the correct revisions are returned and are in chronological order
1785
+    result = self.http_post( "/notebooks/load_note_revisions/", dict(
1786
+      notebook_id = self.notebook.object_id,
1787
+      note_id = self.note.object_id,
1788
+    ), session_id = self.session_id )
1789
+
1790
+    revisions = result[ "revisions" ]
1791
+    assert revisions != None
1792
+    assert len( revisions ) == 3
1793
+    assert revisions[ 1 ].revision == previous_revision
1794
+    assert revisions[ 1 ].user_id == self.user.object_id
1795
+    assert revisions[ 1 ].username == self.username
1796
+    assert revisions[ 2 ].revision == current_revision
1797
+    assert revisions[ 2 ].user_id == self.user.object_id
1798
+    assert revisions[ 2 ].username == self.username
1799
+
1730 1800
   def test_save_note_by_different_user( self, startup = False ):
1731 1801
     self.login2()
1732 1802
 
@@ -1796,6 +1866,27 @@ class Test_notebooks( Test_controller ):
1796 1866
     assert revisions[ 2 ].user_id == self.user2.object_id
1797 1867
     assert revisions[ 2 ].username == self.username2
1798 1868
 
1869
+  def test_save_note_by_different_user_with_notebook_read_write_for_own_notes( self ):
1870
+    self.login2()
1871
+
1872
+    self.database.execute( self.user2.sql_update_access( 
1873
+      self.notebook.object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = True,
1874
+    ) )
1875
+
1876
+    previous_revision = self.note.revision
1877
+    new_note_contents = u"<h3>new title</h3>new blah"
1878
+    result = self.http_post( "/notebooks/save_note/", dict(
1879
+      notebook_id = self.notebook.object_id,
1880
+      note_id = self.note.object_id,
1881
+      contents = new_note_contents,
1882
+      startup = False,
1883
+      previous_revision = previous_revision,
1884
+    ), session_id = self.session_id )
1885
+
1886
+    assert result.get( "error" )
1887
+    user = self.database.load( User, self.user.object_id )
1888
+    assert user.storage_bytes == 0
1889
+
1799 1890
   def test_save_note_without_login( self, startup = False ):
1800 1891
     # save over an existing note supplying new contents and a new title
1801 1892
     previous_revision = self.note.revision
@@ -3688,7 +3779,7 @@ class Test_notebooks( Test_controller ):
3688 3779
     assert isinstance( notebook, Notebook )
3689 3780
     assert notebook.object_id == new_notebook_id
3690 3781
     assert notebook.name == u"new notebook"
3691
-    assert notebook.read_write == True
3782
+    assert notebook.read_write == Notebook.READ_WRITE
3692 3783
     assert notebook.owner == True
3693 3784
     assert notebook.trash_id
3694 3785
 
@@ -3715,7 +3806,7 @@ class Test_notebooks( Test_controller ):
3715 3806
     assert result[ "invites" ] == []
3716 3807
 
3717 3808
     assert notebook.object_id == new_notebook_id
3718
-    assert notebook.read_write == True
3809
+    assert notebook.read_write == Notebook.READ_WRITE
3719 3810
     assert notebook.owner == True
3720 3811
 
3721 3812
   def test_create_without_login( self ):
@@ -3810,7 +3901,7 @@ class Test_notebooks( Test_controller ):
3810 3901
     assert isinstance( notebook, Notebook )
3811 3902
     assert notebook.object_id == remaining_notebook_id
3812 3903
     assert notebook.name == u"my notebook"
3813
-    assert notebook.read_write == True
3904
+    assert notebook.read_write == Notebook.READ_WRITE
3814 3905
     assert notebook.owner == True
3815 3906
     assert notebook.trash_id
3816 3907
     assert notebook.user_id == self.user.object_id
@@ -3864,7 +3955,7 @@ class Test_notebooks( Test_controller ):
3864 3955
     assert isinstance( notebook, Notebook )
3865 3956
     assert notebook.object_id == remaining_notebook_id
3866 3957
     assert notebook.name == u"my notebook"
3867
-    assert notebook.read_write == True
3958
+    assert notebook.read_write == Notebook.READ_WRITE
3868 3959
     assert notebook.owner == True
3869 3960
     assert notebook.trash_id
3870 3961
     assert notebook.user_id == self.user.object_id
@@ -4009,7 +4100,7 @@ class Test_notebooks( Test_controller ):
4009 4100
     assert isinstance( notebook, Notebook )
4010 4101
     assert notebook.object_id == notebook_id
4011 4102
     assert notebook.name == self.notebook.name
4012
-    assert notebook.read_write == True
4103
+    assert notebook.read_write == Notebook.READ_WRITE
4013 4104
     assert notebook.owner == True
4014 4105
     assert notebook.trash_id
4015 4106
     assert notebook.user_id == self.user.object_id
@@ -4069,7 +4160,7 @@ class Test_notebooks( Test_controller ):
4069 4160
     assert isinstance( notebook, Notebook )
4070 4161
     assert notebook.object_id == notebook_id
4071 4162
     assert notebook.name == self.notebook.name
4072
-    assert notebook.read_write == True
4163
+    assert notebook.read_write == Notebook.READ_WRITE
4073 4164
     assert notebook.owner == True
4074 4165
     assert notebook.trash_id
4075 4166
     assert notebook.user_id == self.user.object_id
@@ -4511,7 +4602,7 @@ class Test_notebooks( Test_controller ):
4511 4602
 
4512 4603
     assert notebook.name == u"imported notebook"
4513 4604
     assert notebook.trash_id
4514
-    assert notebook.read_write is True
4605
+    assert notebook.read_write is Notebook.READ_WRITE
4515 4606
     assert notebook.owner is True
4516 4607
     assert notebook.deleted is False
4517 4608
     assert notebook.user_id == self.user.object_id

+ 6
- 6
controller/test/Test_root.py View File

@@ -62,10 +62,10 @@ class Test_root( Test_controller ):
62 62
 
63 63
     self.anonymous = User.create( self.database.next_id( User ), u"anonymous" )
64 64
     self.database.save( self.anonymous )
65
-    self.database.execute( self.anonymous.sql_save_notebook( self.anon_notebook.object_id, read_write = False, owner = False ) )
66
-    self.database.execute( self.anonymous.sql_save_notebook( self.blog_notebook.object_id, read_write = False, owner = False ) )
67
-    self.database.execute( self.anonymous.sql_save_notebook( self.guide_notebook.object_id, read_write = False, owner = False ) )
68
-    self.database.execute( self.anonymous.sql_save_notebook( self.privacy_notebook.object_id, read_write = False, owner = False ) )
65
+    self.database.execute( self.anonymous.sql_save_notebook( self.anon_notebook.object_id, read_write = False, owner = False, rank = 0 ) )
66
+    self.database.execute( self.anonymous.sql_save_notebook( self.blog_notebook.object_id, read_write = False, owner = False, rank = 1 ) )
67
+    self.database.execute( self.anonymous.sql_save_notebook( self.guide_notebook.object_id, read_write = False, owner = False, rank = 2 ) )
68
+    self.database.execute( self.anonymous.sql_save_notebook( self.privacy_notebook.object_id, read_write = False, owner = False, rank = 3 ) )
69 69
 
70 70
   def test_index( self ):
71 71
     result = self.http_get( "/" )
@@ -429,7 +429,7 @@ class Test_root( Test_controller ):
429 429
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.anon_notebook.object_id ][ 0 ]
430 430
     assert notebook.object_id == self.anon_notebook.object_id
431 431
     assert notebook.name == self.anon_notebook.name
432
-    assert notebook.read_write == False
432
+    assert notebook.read_write == Notebook.READ_ONLY
433 433
     assert notebook.owner == False
434 434
 
435 435
     rate_plan = result[ u"rate_plan" ]
@@ -451,7 +451,7 @@ class Test_root( Test_controller ):
451 451
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebook.object_id ][ 0 ]
452 452
     assert notebook.object_id == self.notebook.object_id
453 453
     assert notebook.name == self.notebook.name
454
-    assert notebook.read_write == True
454
+    assert notebook.read_write == Notebook.READ_WRITE
455 455
     assert notebook.owner == True
456 456
 
457 457
     rate_plan = result[ u"rate_plan" ]

+ 246
- 74
controller/test/Test_users.py View File

@@ -62,21 +62,23 @@ class Test_users( Test_controller ):
62 62
     self.database.save( self.notebooks[ 0 ] )
63 63
     self.database.save( self.notebooks[ 1 ] )
64 64
 
65
+    self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
66
+    self.database.save( self.user, commit = False )
67
+
65 68
     self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"anon notebook" )
66 69
     self.database.save( self.anon_notebook )
67 70
     self.startup_note = Note.create(
68 71
       self.database.next_id( Note ), u"<h3>login</h3>",
69 72
       notebook_id = self.anon_notebook.object_id, startup = True,
73
+      user_id = self.user.object_id,
70 74
     )
71
-    self.database.save( self.startup_note )
75
+    self.database.save( self.startup_note, commit = False )
72 76
 
73 77
     self.group = Group.create( self.database.next_id( Group ), u"my group" )
74 78
     self.database.save( self.group, commit = False )
75 79
     self.group2 = Group.create( self.database.next_id( Group ), u"other group" )
76 80
     self.database.save( self.group2, commit = False )
77 81
 
78
-    self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
79
-    self.database.save( self.user, commit = False )
80 82
     self.database.execute( self.user.sql_save_notebook( notebook_id1, read_write = True, owner = True, rank = 0 ), commit = False )
81 83
     self.database.execute( self.user.sql_save_notebook( trash_id1, read_write = True, owner = True ), commit = False )
82 84
     self.database.execute( self.user.sql_save_notebook( notebook_id2, read_write = True, owner = True, rank = 1 ), commit = False )
@@ -180,7 +182,7 @@ class Test_users( Test_controller ):
180 182
     assert notebook.revision
181 183
     assert notebook.name == u"trash"
182 184
     assert notebook.trash_id == None
183
-    assert notebook.read_write == True
185
+    assert notebook.read_write == Notebook.READ_WRITE
184 186
     assert notebook.owner == True
185 187
     assert notebook.rank == None
186 188
 
@@ -189,7 +191,7 @@ class Test_users( Test_controller ):
189 191
     assert notebook.revision
190 192
     assert notebook.name == u"my notebook"
191 193
     assert notebook.trash_id
192
-    assert notebook.read_write == True
194
+    assert notebook.read_write == Notebook.READ_WRITE
193 195
     assert notebook.owner == True
194 196
     assert notebook.rank == 0
195 197
 
@@ -198,7 +200,7 @@ class Test_users( Test_controller ):
198 200
     assert notebook.revision == self.anon_notebook.revision
199 201
     assert notebook.name == self.anon_notebook.name
200 202
     assert notebook.trash_id == None
201
-    assert notebook.read_write == False
203
+    assert notebook.read_write == Notebook.READ_ONLY
202 204
     assert notebook.owner == False
203 205
     assert notebook.rank == None
204 206
 
@@ -250,8 +252,8 @@ class Test_users( Test_controller ):
250 252
     assert result[ u"user" ].username == self.new_username
251 253
     assert result[ u"user" ].email_address == self.new_email_address
252 254
 
253
-    assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].object_id )
254
-    assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].trash_id )
255
+    assert cherrypy.root.users.load_notebook( user.object_id, self.notebooks[ 0 ].object_id )
256
+    assert cherrypy.root.users.load_notebook( user.object_id, self.notebooks[ 0 ].trash_id )
255 257
 
256 258
     # the notebook that the user was invited to should be in the list of returned notebooks
257 259
     notebooks = dict( [ ( notebook.object_id, notebook ) for notebook in result[ u"notebooks" ] ] )
@@ -261,7 +263,7 @@ class Test_users( Test_controller ):
261 263
     assert notebook.revision
262 264
     assert notebook.name == self.notebooks[ 0 ].name
263 265
     assert notebook.trash_id
264
-    assert notebook.read_write == False
266
+    assert notebook.read_write == Notebook.READ_ONLY
265 267
     assert notebook.owner == False
266 268
     assert notebook.rank == 1
267 269
 
@@ -269,7 +271,7 @@ class Test_users( Test_controller ):
269 271
     assert notebook.revision
270 272
     assert notebook.name == u"trash"
271 273
     assert notebook.trash_id == None
272
-    assert notebook.read_write == False
274
+    assert notebook.read_write == Notebook.READ_ONLY
273 275
     assert notebook.owner == False
274 276
     assert notebook.rank == None
275 277
 
@@ -277,7 +279,7 @@ class Test_users( Test_controller ):
277 279
     assert notebook.revision == self.anon_notebook.revision
278 280
     assert notebook.name == self.anon_notebook.name
279 281
     assert notebook.trash_id == None
280
-    assert notebook.read_write == False
282
+    assert notebook.read_write == Notebook.READ_ONLY
281 283
     assert notebook.owner == False
282 284
     assert notebook.rank == None
283 285
 
@@ -317,7 +319,7 @@ class Test_users( Test_controller ):
317 319
     assert notebook.revision
318 320
     assert notebook.name == u"trash"
319 321
     assert notebook.trash_id == None
320
-    assert notebook.read_write == True
322
+    assert notebook.read_write == Notebook.READ_WRITE
321 323
     assert notebook.owner == True
322 324
     assert notebook.rank == None
323 325
 
@@ -326,7 +328,7 @@ class Test_users( Test_controller ):
326 328
     assert notebook.revision
327 329
     assert notebook.name == u"my notebook"
328 330
     assert notebook.trash_id
329
-    assert notebook.read_write == True
331
+    assert notebook.read_write == Notebook.READ_WRITE
330 332
     assert notebook.owner == True
331 333
     assert notebook.rank == 0
332 334
 
@@ -335,7 +337,7 @@ class Test_users( Test_controller ):
335 337
     assert notebook.revision == self.anon_notebook.revision
336 338
     assert notebook.name == self.anon_notebook.name
337 339
     assert notebook.trash_id == None
338
-    assert notebook.read_write == False
340
+    assert notebook.read_write == Notebook.READ_ONLY
339 341
     assert notebook.owner == False
340 342
     assert notebook.rank == None
341 343
 
@@ -639,7 +641,7 @@ class Test_users( Test_controller ):
639 641
     assert notebook.revision
640 642
     assert notebook.name == u"trash"
641 643
     assert notebook.trash_id == None
642
-    assert notebook.read_write == True
644
+    assert notebook.read_write == Notebook.READ_WRITE
643 645
     assert notebook.owner == True
644 646
     assert notebook.rank == None
645 647
 
@@ -648,7 +650,7 @@ class Test_users( Test_controller ):
648 650
     assert notebook.revision
649 651
     assert notebook.name == u"my notebook"
650 652
     assert notebook.trash_id
651
-    assert notebook.read_write == True
653
+    assert notebook.read_write == Notebook.READ_WRITE
652 654
     assert notebook.owner == True
653 655
     assert notebook.rank == 0
654 656
 
@@ -657,7 +659,7 @@ class Test_users( Test_controller ):
657 659
     assert notebook.revision == self.anon_notebook.revision
658 660
     assert notebook.name == self.anon_notebook.name
659 661
     assert notebook.trash_id == None
660
-    assert notebook.read_write == False
662
+    assert notebook.read_write == Notebook.READ_ONLY
661 663
     assert notebook.owner == False
662 664
     assert notebook.rank == None
663 665
 
@@ -740,27 +742,27 @@ class Test_users( Test_controller ):
740 742
     assert len( result[ u"notebooks" ] ) == 5
741 743
     assert result[ u"notebooks" ][ 0 ].object_id
742 744
     assert result[ u"notebooks" ][ 0 ].name == u"trash"
743
-    assert result[ u"notebooks" ][ 0 ].read_write == True
745
+    assert result[ u"notebooks" ][ 0 ].read_write == Notebook.READ_WRITE
744 746
     assert result[ u"notebooks" ][ 0 ].owner == True
745 747
     assert result[ u"notebooks" ][ 0 ].rank == None
746 748
     assert result[ u"notebooks" ][ 1 ].object_id
747 749
     assert result[ u"notebooks" ][ 1 ].name == u"trash"
748
-    assert result[ u"notebooks" ][ 1 ].read_write == True
750
+    assert result[ u"notebooks" ][ 1 ].read_write == Notebook.READ_WRITE
749 751
     assert result[ u"notebooks" ][ 1 ].owner == True
750 752
     assert result[ u"notebooks" ][ 1 ].rank == None
751 753
     assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 0 ].object_id
752 754
     assert result[ u"notebooks" ][ 2 ].name == self.notebooks[ 0 ].name
753
-    assert result[ u"notebooks" ][ 2 ].read_write == True
755
+    assert result[ u"notebooks" ][ 2 ].read_write == Notebook.READ_WRITE
754 756
     assert result[ u"notebooks" ][ 2 ].owner == True
755 757
     assert result[ u"notebooks" ][ 2 ].rank == 0
756 758
     assert result[ u"notebooks" ][ 3 ].object_id == self.notebooks[ 1 ].object_id
757 759
     assert result[ u"notebooks" ][ 3 ].name == self.notebooks[ 1 ].name
758
-    assert result[ u"notebooks" ][ 3 ].read_write == True
760
+    assert result[ u"notebooks" ][ 3 ].read_write == Notebook.READ_WRITE
759 761
     assert result[ u"notebooks" ][ 3 ].owner == True
760 762
     assert result[ u"notebooks" ][ 3 ].rank == 1
761 763
     assert result[ u"notebooks" ][ 4 ].object_id == self.anon_notebook.object_id
762 764
     assert result[ u"notebooks" ][ 4 ].name == self.anon_notebook.name
763
-    assert result[ u"notebooks" ][ 4 ].read_write == False
765
+    assert result[ u"notebooks" ][ 4 ].read_write == Notebook.READ_ONLY
764 766
     assert result[ u"notebooks" ][ 4 ].owner == False
765 767
     assert result[ u"notebooks" ][ 4 ].rank == None
766 768
     assert result[ u"login_url" ] is None
@@ -783,7 +785,7 @@ class Test_users( Test_controller ):
783 785
     assert len( result[ u"notebooks" ] ) == 1
784 786
     assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
785 787
     assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
786
-    assert result[ u"notebooks" ][ 0 ].read_write == False
788
+    assert result[ u"notebooks" ][ 0 ].read_write == Notebook.READ_ONLY
787 789
     assert result[ u"notebooks" ][ 0 ].owner == False
788 790
     assert result[ u"notebooks" ][ 0 ].rank == None
789 791
 
@@ -831,8 +833,8 @@ class Test_users( Test_controller ):
831 833
     invite_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
832 834
     assert invite_notebook_id == self.notebooks[ 0 ].object_id
833 835
 
834
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
835
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
836
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
837
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
836 838
 
837 839
   def test_login_with_after_login( self ):
838 840
     after_login = u"/foo/bar"
@@ -895,45 +897,215 @@ class Test_users( Test_controller ):
895 897
     assert user.group_storage_bytes == 0
896 898
     assert user.revision > previous_revision
897 899
 
898
-  def test_check_access( self ):
899
-    access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id )
900
+  def test_load_notebook( self ):
901
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id )
900 902
 
901
-    assert access is True
903
+    assert notebook
904
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
902 905
 
903
-  def test_check_access_read_write( self ):
904
-    access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True )
906
+  def test_load_notebook_unknown_notebook( self ):
907
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, u"unknownid" )
905 908
 
906
-    assert access is True
909
+    assert notebook is None
907 910
 
908
-  def test_check_access_owner( self ):
909
-    access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, owner = True )
911
+  def test_load_notebook_unknown_user( self ):
912
+    notebook = cherrypy.root.users.load_notebook( u"unknownuser", self.notebooks[ 0 ].object_id )
910 913
 
911
-    assert access is True
914
+    assert notebook is None
912 915
 
913
-  def test_check_access_full( self ):
914
-    access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True, owner = True )
916
+  def test_load_notebook_read_write( self ):
917
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True )
915 918
 
916
-    assert access is True
919
+    assert notebook
920
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
917 921
 
918
-  def test_check_access_anon( self ):
919
-    access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id )
922
+  def test_load_notebook_owner( self ):
923
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id, owner = True )
920 924
 
921
-    assert access is True
925
+    assert notebook
926
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
927
+
928
+  def test_load_notebook_full( self ):
929
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True, owner = True )
930
+
931
+    assert notebook
932
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
933
+
934
+  def test_load_notebook_with_note_id( self ):
935
+    note = Note.create(
936
+      self.database.next_id( Note ), u"<h3>hi</h3>",
937
+      notebook_id = self.notebooks[ 0 ].object_id,
938
+      user_id = self.user.object_id,
939
+    )
940
+    self.database.save( note )
941
+
942
+    self.database.execute( self.user.sql_update_access(
943
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
944
+    ) )
945
+
946
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
947
+                                                  note_id = note.object_id )
948
+
949
+    assert notebook
950
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
951
+
952
+  def test_load_notebook_with_note_id_by_another_user( self ):
953
+    note = Note.create(
954
+      self.database.next_id( Note ), u"<h3>hi from another user</h3>",
955
+      notebook_id = self.notebooks[ 0 ].object_id,
956
+      user_id = self.user2.object_id,
957
+    )
958
+    self.database.save( note )
959
+
960
+    self.database.execute( self.user.sql_update_access(
961
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
962
+    ) )
963
+
964
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
965
+                                                  note_id = note.object_id )
966
+
967
+    assert notebook is None
968
+
969
+  def test_load_notebook_with_unknown_note_id( self ):
970
+    self.database.execute( self.user.sql_update_access(
971
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
972
+    ) )
973
+
974
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
975
+                                                  note_id = u"unknownid" )
976
+
977
+    assert notebook is None
978
+
979
+  def test_load_notebook_with_note_id_in_another_notebook( self ):
980
+    self.database.execute( self.user.sql_update_access(
981
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
982
+    ) )
983
+
984
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
985
+                                                  note_id = self.startup_note.object_id )
986
+
987
+    assert notebook is None
988
+
989
+  def test_load_notebook_read_write_with_note_id( self ):
990
+    note = Note.create(
991
+      self.database.next_id( Note ), u"<h3>hi</h3>",
992
+      notebook_id = self.notebooks[ 0 ].object_id,
993
+      user_id = self.user.object_id,
994
+    )
995
+    self.database.save( note )
996
+
997
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
998
+                                                  note_id = note.object_id )
999
+
1000
+    assert notebook
1001
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1002
+
1003
+  def test_load_notebook_read_write_with_note_id_by_another_user( self ):
1004
+    note = Note.create(
1005
+      self.database.next_id( Note ), u"<h3>hi from another user</h3>",
1006
+      notebook_id = self.notebooks[ 0 ].object_id,
1007
+      user_id = self.user2.object_id,
1008
+    )
1009
+    self.database.save( note )
1010
+
1011
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1012
+                                                  note_id = note.object_id )
1013
+
1014
+    assert notebook
1015
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1016
+
1017
+  def test_load_notebook_read_write_with_unknown_note_id( self ):
1018
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1019
+                                                  note_id = u"unknownid" )
1020
+
1021
+    assert notebook
1022
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1023
+
1024
+  def test_load_notebook_read_write_with_note_id_in_another_notebook( self ):
1025
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1026
+                                                  note_id = self.startup_note.object_id )
1027
+
1028
+    assert notebook
1029
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1030
+
1031
+  def test_load_notebook_read_only_with_note_id( self ):
1032
+    note = Note.create(
1033
+      self.database.next_id( Note ), u"<h3>hi</h3>",
1034
+      notebook_id = self.notebooks[ 0 ].object_id,
1035
+      user_id = self.user.object_id,
1036
+    )
1037
+    self.database.save( note )
1038
+
1039
+    self.database.execute( self.user.sql_update_access(
1040
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
1041
+    ) )
1042
+
1043
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1044
+                                                  note_id = note.object_id )
1045
+
1046
+    assert notebook
1047
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1048
+
1049
+  def test_load_notebook_read_only_with_note_id_by_another_user( self ):
1050
+    note = Note.create(
1051
+      self.database.next_id( Note ), u"<h3>hi from another user</h3>",
1052
+      notebook_id = self.notebooks[ 0 ].object_id,
1053
+      user_id = self.user2.object_id,
1054
+    )
1055
+    self.database.save( note )
1056
+
1057
+    self.database.execute( self.user.sql_update_access(
1058
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
1059
+    ) )
1060
+
1061
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1062
+                                                  note_id = note.object_id )
1063
+
1064
+    assert notebook
1065
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1066
+
1067
+  def test_load_notebook_read_only_with_unknown_note_id( self ):
1068
+    self.database.execute( self.user.sql_update_access(
1069
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
1070
+    ) )
1071
+
1072
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1073
+                                                  note_id = u"unknownid" )
1074
+
1075
+    assert notebook
1076
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1077
+
1078
+  def test_load_notebook_read_only_with_note_id_in_another_notebook( self ):
1079
+    self.database.execute( self.user.sql_update_access(
1080
+      self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
1081
+    ) )
1082
+
1083
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
1084
+                                                  note_id = self.startup_note.object_id )
1085
+
1086
+    assert notebook
1087
+    assert notebook.object_id == self.notebooks[ 0 ].object_id
1088
+
1089
+  def test_load_notebook_anon( self ):
1090
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id )
1091
+
1092
+    assert notebook
1093
+    assert notebook.object_id == self.anon_notebook.object_id
922 1094
 
923
-  def test_check_access_anon_read_write( self ):
924
-    access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, read_write = True )
1095
+  def test_load_notebook_anon_read_write( self ):
1096
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id, read_write = True )
925 1097
 
926
-    assert access is False
1098
+    assert notebook is None
927 1099
 
928
-  def test_check_access_anon_owner( self ):
929
-    access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, owner = True )
1100
+  def test_load_notebook_anon_owner( self ):
1101
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id, owner = True )
930 1102
 
931
-    assert access is False
1103
+    assert notebook is None
932 1104
 
933
-  def test_check_access_anon_full( self ):
934
-    access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, read_write = True, owner = True )
1105
+  def test_load_notebook_anon_full( self ):
1106
+    notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id, read_write = True, owner = True )
935 1107
 
936
-    assert access is False
1108
+    assert notebook is None
937 1109
 
938 1110
   def test_check_group( self ):
939 1111
     membership = cherrypy.root.users.check_group( self.user.object_id, self.group.object_id )
@@ -1097,7 +1269,7 @@ class Test_users( Test_controller ):
1097 1269
     assert len( result[ u"notebooks" ] ) == 1
1098 1270
     assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
1099 1271
     assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
1100
-    assert result[ u"notebooks" ][ 0 ].read_write == False
1272
+    assert result[ u"notebooks" ][ 0 ].read_write == Notebook.READ_ONLY
1101 1273
     assert result[ u"notebooks" ][ 0 ].owner == False
1102 1274
     assert result[ u"notebooks" ][ 0 ].rank == None
1103 1275
 
@@ -2151,8 +2323,8 @@ class Test_users( Test_controller ):
2151 2323
       invite_id = invite_id,
2152 2324
     ), session_id = self.session_id )
2153 2325
 
2154
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
2155
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2326
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
2327
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2156 2328
 
2157 2329
     self.login()
2158 2330
     result = self.http_post( "/users/revoke_invite", dict(
@@ -2163,8 +2335,8 @@ class Test_users( Test_controller ):
2163 2335
     assert result[ u"message" ]
2164 2336
     assert len( result[ u"invites" ] ) == 0
2165 2337
 
2166
-    assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
2167
-    assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2338
+    assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
2339
+    assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2168 2340
 
2169 2341
   def test_revoke_invite_redeemed_self( self ):
2170 2342
     self.login()
@@ -2191,8 +2363,8 @@ class Test_users( Test_controller ):
2191 2363
       invite_id = invite_id,
2192 2364
     ), session_id = self.session_id )
2193 2365
 
2194
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
2195
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2366
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
2367
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2196 2368
 
2197 2369
     # as user2, revoke that user's own invite
2198 2370
     result = self.http_post( "/users/revoke_invite", dict(
@@ -2204,8 +2376,8 @@ class Test_users( Test_controller ):
2204 2376
     assert len( result[ u"invites" ] ) == 0
2205 2377
 
2206 2378
     # the user should no longer have any access
2207
-    assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
2208
-    assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2379
+    assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
2380
+    assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2209 2381
 
2210 2382
   def test_revoke_invite_without_login( self ):
2211 2383
     # login to send the invites, but don't send the logged-in session id for revoke_invite() below
@@ -2347,8 +2519,8 @@ class Test_users( Test_controller ):
2347 2519
     ), session_id = self.session_id )
2348 2520
 
2349 2521
     # assert that access has been granted
2350
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
2351
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2522
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
2523
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2352 2524
 
2353 2525
     # assert that the user is redirected to the notebook that the invite is for
2354 2526
     assert result[ u"redirect"].startswith( u"/notebooks/" )
@@ -2385,8 +2557,8 @@ class Test_users( Test_controller ):
2385 2557
     ), session_id = self.session_id )
2386 2558
 
2387 2559
     # assert that access is still granted
2388
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
2389
-    assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2560
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
2561
+    assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
2390 2562
 
2391 2563
     # assert that the user is redirected to the notebook that the invite is for
2392 2564
     assert result[ u"redirect"].startswith( u"/notebooks/" )
@@ -4104,7 +4276,7 @@ class Test_users( Test_controller ):
4104 4276
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4105 4277
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4106 4278
     assert notebook.name == self.notebooks[ 0 ].name
4107
-    assert notebook.read_write == True
4279
+    assert notebook.read_write == Notebook.READ_WRITE
4108 4280
     assert notebook.owner == True
4109 4281
     assert notebook.rank == 0
4110 4282
 
@@ -4145,7 +4317,7 @@ class Test_users( Test_controller ):
4145 4317
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4146 4318
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4147 4319
     assert notebook.name == self.notebooks[ 0 ].name
4148
-    assert notebook.read_write == True
4320
+    assert notebook.read_write == Notebook.READ_WRITE
4149 4321
     assert notebook.owner == True
4150 4322
     assert notebook.rank == 0
4151 4323
 
@@ -4184,7 +4356,7 @@ class Test_users( Test_controller ):
4184 4356
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4185 4357
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4186 4358
     assert notebook.name == self.notebooks[ 0 ].name
4187
-    assert notebook.read_write == True
4359
+    assert notebook.read_write == Notebook.READ_WRITE
4188 4360
     assert notebook.owner == True
4189 4361
     assert notebook.rank == 0
4190 4362
 
@@ -4224,7 +4396,7 @@ class Test_users( Test_controller ):
4224 4396
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4225 4397
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4226 4398
     assert notebook.name == self.notebooks[ 0 ].name
4227
-    assert notebook.read_write == True
4399
+    assert notebook.read_write == Notebook.READ_WRITE
4228 4400
     assert notebook.owner == True
4229 4401
     assert notebook.rank == 0
4230 4402
 
@@ -4262,7 +4434,7 @@ class Test_users( Test_controller ):
4262 4434
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4263 4435
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4264 4436
     assert notebook.name == self.notebooks[ 0 ].name
4265
-    assert notebook.read_write == True
4437
+    assert notebook.read_write == Notebook.READ_WRITE
4266 4438
     assert notebook.owner == True
4267 4439
     assert notebook.rank == 0
4268 4440
 
@@ -4307,7 +4479,7 @@ class Test_users( Test_controller ):
4307 4479
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4308 4480
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4309 4481
     assert notebook.name == self.notebooks[ 0 ].name
4310
-    assert notebook.read_write == True
4482
+    assert notebook.read_write == Notebook.READ_WRITE
4311 4483
     assert notebook.owner == True
4312 4484
     assert notebook.rank == 0
4313 4485
 
@@ -4436,7 +4608,7 @@ class Test_users( Test_controller ):
4436 4608
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4437 4609
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4438 4610
     assert notebook.name == self.notebooks[ 0 ].name
4439
-    assert notebook.read_write == True
4611
+    assert notebook.read_write == Notebook.READ_WRITE
4440 4612
     assert notebook.owner == True
4441 4613
     assert notebook.rank == 0
4442 4614
 
@@ -4484,7 +4656,7 @@ class Test_users( Test_controller ):
4484 4656
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4485 4657
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4486 4658
     assert notebook.name == self.notebooks[ 0 ].name
4487
-    assert notebook.read_write == True
4659
+    assert notebook.read_write == Notebook.READ_WRITE
4488 4660
     assert notebook.owner == True
4489 4661
     assert notebook.rank == 0
4490 4662
 
@@ -4532,7 +4704,7 @@ class Test_users( Test_controller ):
4532 4704
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4533 4705
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4534 4706
     assert notebook.name == self.notebooks[ 0 ].name
4535
-    assert notebook.read_write == True
4707
+    assert notebook.read_write == Notebook.READ_WRITE
4536 4708
     assert notebook.owner == True
4537 4709
     assert notebook.rank == 0
4538 4710
 
@@ -4578,7 +4750,7 @@ class Test_users( Test_controller ):
4578 4750
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4579 4751
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4580 4752
     assert notebook.name == self.notebooks[ 0 ].name
4581
-    assert notebook.read_write == True
4753
+    assert notebook.read_write == Notebook.READ_WRITE
4582 4754
     assert notebook.owner == True
4583 4755
     assert notebook.rank == 0
4584 4756
 
@@ -4627,7 +4799,7 @@ class Test_users( Test_controller ):
4627 4799
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4628 4800
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4629 4801
     assert notebook.name == self.notebooks[ 0 ].name
4630
-    assert notebook.read_write == True
4802
+    assert notebook.read_write == Notebook.READ_WRITE
4631 4803
     assert notebook.owner == True
4632 4804
     assert notebook.rank == 0
4633 4805
 
@@ -4675,7 +4847,7 @@ class Test_users( Test_controller ):
4675 4847
     notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
4676 4848
     assert notebook.object_id == self.notebooks[ 0 ].object_id
4677 4849
     assert notebook.name == self.notebooks[ 0 ].name
4678
-    assert notebook.read_write == True
4850
+    assert notebook.read_write == Notebook.READ_WRITE
4679 4851
     assert notebook.owner == True
4680 4852
     assert notebook.rank == 0
4681 4853
 

+ 7
- 0
luminotes.py View File

@@ -9,8 +9,10 @@ import urllib2 as urllib
9 9
 import cherrypy
10 10
 import webbrowser
11 11
 from controller.Database import Database
12
+from controller.Schema_upgrader import Schema_upgrader
12 13
 from controller.Root import Root
13 14
 from config import Common
15
+from config.Version import VERSION
14 16
 
15 17
 
16 18
 INITIAL_SOCKET_TIMEOUT_SECONDS = 1
@@ -103,6 +105,10 @@ def main( args ):
103 105
     ssl_mode = cherrypy.config.configMap[ u"global" ].get( u"luminotes.db_ssl_mode" ),
104 106
   )
105 107
 
108
+  # if necessary, upgrade the database schema to match this current version of the code
109
+  schema_upgrader = Schema_upgrader( database )
110
+  schema_upgrader.upgrade_schema( to_version = VERSION )
111
+
106 112
   cherrypy.lowercase_api = True
107 113
   root = Root( database, cherrypy.config.configMap )
108 114
   cherrypy.root = root
@@ -114,6 +120,7 @@ def callback( log_access_file, log_file, server_url, port_filename, socket_port,
114 120
   # record our listening socket port
115 121
   if port_filename:
116 122
     port_file = file( port_filename, "w" )
123
+    os.chmod( port_filename, stat.S_IRUSR | stat.S_IWUSR )
117 124
     port_file.write( "%s" % socket_port )
118 125
     port_file.close()
119 126
 

+ 67
- 5
model/Notebook.py View File

@@ -12,8 +12,12 @@ class Notebook( Persistent ):
12 12
   WHITESPACE_PATTERN = re.compile( r"\s+" )
13 13
   SEARCH_OPERATORS = re.compile( r"[&|!()'\\:]" )
14 14
 
15
+  READ_ONLY = 0                # user can only view the notes within this notebook
16
+  READ_WRITE = 1               # user can view and edit the notes within this notebook
17
+  READ_WRITE_FOR_OWN_NOTES = 2 # user can only edit their own notes, not notes created by others
18
+
15 19
   def __init__( self, object_id, revision = None, name = None, trash_id = None, deleted = False,
16
-                user_id = None, read_write = True, owner = True, rank = None ):
20
+                user_id = None, read_write = None, owner = True, rank = None, own_notes_only = False ):
17 21
     """
18 22
     Create a new notebook with the given id and name.
19 23
 
@@ -30,11 +34,14 @@ class Notebook( Persistent ):
30 34
     @type user_id: unicode or NoneType
31 35
     @param user_id: id of the user who most recently updated this notebook object (optional)
32 36
     @type read_write: bool or NoneType
33
-    @param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
37
+    @param read_write: whether this view of the notebook is currently read-write. one of:
38
+                       READ_ONLY, READ_WRITE, READ_WRITE_FOR_OWN_NOTES (optional, defaults to READ_WRITE)
34 39
     @type owner: bool or NoneType
35 40
     @param owner: whether this view of the notebook currently has owner-level access (optional, defaults to True)
36 41
     @type rank: float or NoneType
37 42
     @param rank: indicates numeric ordering of this note in relation to other notebooks
43
+    @type own_notes_only: bool or NoneType
44
+    @param own_notes_only: True makes read_write be READ_WRITE_FOR_OWN_NOTES (optional, defaults to False)
38 45
     @rtype: Notebook
39 46
     @return: newly constructed notebook
40 47
     """
@@ -43,12 +50,22 @@ class Notebook( Persistent ):
43 50
     self.__trash_id = trash_id
44 51
     self.__deleted = deleted
45 52
     self.__user_id = user_id
53
+
54
+    read_write = {
55
+      None: Notebook.READ_WRITE,
56
+      True: Notebook.READ_WRITE,
57
+      False: Notebook.READ_ONLY,
58
+    }.get( read_write, read_write )
59
+
60
+    if own_notes_only is True and read_write != Notebook.READ_ONLY:
61
+      read_write = Notebook.READ_WRITE_FOR_OWN_NOTES
62
+
46 63
     self.__read_write = read_write
47 64
     self.__owner = owner
48 65
     self.__rank = rank
49 66
 
50 67
   @staticmethod
51
-  def create( object_id, name = None, trash_id = None, deleted = False, user_id = None, read_write = True, owner = True, rank = None ):
68
+  def create( object_id, name = None, trash_id = None, deleted = False, user_id = None, read_write = None, owner = True, rank = None, own_notes_only = False ):
52 69
     """
53 70
     Convenience constructor for creating a new notebook.
54 71
 
@@ -63,15 +80,18 @@ class Notebook( Persistent ):
63 80
     @type user_id: unicode or NoneType
64 81
     @param user_id: id of the user who most recently updated this notebook object (optional)
65 82
     @type read_write: bool or NoneType
66
-    @param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
83
+    @param read_write: whether this view of the notebook is currently read-write. one of:
84
+                       READ_ONLY, READ_WRITE, READ_WRITE_FOR_OWN_NOTES (optional, defaults to READ_WRITE)
67 85
     @type owner: bool or NoneType
68 86
     @param owner: whether this view of the notebook currently has owner-level access (optional, defaults to True)
69 87
     @type rank: float or NoneType
70 88
     @param rank: indicates numeric ordering of this note in relation to other notebooks
89
+    @type own_notes_only: bool or NoneType
90
+    @param own_notes_only: True makes read_write be READ_WRITE_FOR_OWN_NOTES (optional, defaults to False)
71 91
     @rtype: Notebook
72 92
     @return: newly constructed notebook
73 93
     """
74
-    return Notebook( object_id, name = name, trash_id = trash_id, user_id = user_id, read_write = read_write, owner = owner, rank = rank )
94
+    return Notebook( object_id, name = name, trash_id = trash_id, user_id = user_id, read_write = read_write, owner = owner, rank = rank, own_notes_only = own_notes_only )
75 95
 
76 96
   @staticmethod
77 97
   def sql_load( object_id, revision = None ):
@@ -264,6 +284,42 @@ class Notebook( Persistent ):
264 284
       "select count( id ) from note_current where notebook_id = %s;" % \
265 285
       ( quote( self.object_id ) )
266 286
 
287
+  def sql_load_tag_by_name( self, user_id, tag_name ):
288
+    """
289
+    Return a SQL string to load a tag associated with this notebook by the given user.
290
+    """
291
+    return \
292
+      """
293
+      select
294
+        tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description, tag_notebook.value
295
+      from
296
+        tag_notebook, tag
297
+      where
298
+        tag_notebook.notebook_id = %s and 
299
+        tag_notebook.user_id = %s and
300
+        tag_notebook.tag_id = tag.id and
301
+        tag.name = %s
302
+      order by tag.name;
303
+      """ % ( quote( self.object_id ), quote( user_id ), quote( tag_name ) )
304
+
305
+  def sql_load_tags( self, user_id ):
306
+    """
307
+    Return a SQL string to load a list of all the tags associated with this notebook by the given
308
+    user.
309
+    """
310
+    return \
311
+      """
312
+      select
313
+        tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description, tag_notebook.value
314
+      from
315
+        tag_notebook, tag
316
+      where
317
+        tag_notebook.notebook_id = %s and 
318
+        tag_notebook.user_id = %s and
319
+        tag_notebook.tag_id = tag.id
320
+      order by tag.name;
321
+      """ % ( quote( self.object_id ), quote( user_id ) )
322
+
267 323
   def to_dict( self ):
268 324
     d = Persistent.to_dict( self )
269 325
 
@@ -285,6 +341,12 @@ class Notebook( Persistent ):
285 341
   def __set_read_write( self, read_write ):
286 342
     # The read_write member isn't actually saved to the database, so setting it doesn't need to
287 343
     # call update_revision().
344
+    read_write = {
345
+      None: Notebook.READ_WRITE,
346
+      True: Notebook.READ_WRITE,
347
+      False: Notebook.READ_ONLY,
348
+    }.get( read_write, read_write )
349
+
288 350
     self.__read_write = read_write
289 351
 
290 352
   def __set_owner( self, owner ):

+ 135
- 0
model/Tag.py View File

@@ -0,0 +1,135 @@
1
+from Persistent import Persistent, quote
2
+
3
+
4
+class Tag( Persistent ):
5
+  """
6
+  A tag for a note or a notebook.
7
+  """
8
+  def __init__( self, object_id, revision = None, notebook_id = None, user_id = None, name = None, description = None, value = None ):
9
+    """
10
+    Create a Tag with the given id.
11
+
12
+    @type object_id: unicode
13
+    @param object_id: id of the Tag
14
+    @type revision: datetime or NoneType
15
+    @param revision: revision timestamp of the object (optional, defaults to now)
16
+    @type notebook_id: unicode or NoneType
17
+    @param notebook_id: id of the notebook whose namespace this tag is in, if any
18
+    @type user_id: unicode or NoneType
19
+    @param user_id: id of the user who most recently updated this tag, if any
20
+    @type name: unicode or NoneType
21
+    @param name: name of the tag (optional)
22
+    @type description: unicode or NoneType
23
+    @param description: brief description of the tag (optional)
24
+    @type value: unicode or NoneType
25
+    @param value: per-note or per-notebook value of the tag (optional)
26
+    @rtype: Tag
27
+    @return: newly constructed Tag
28
+    """
29
+    Persistent.__init__( self, object_id, revision )
30
+    self.__notebook_id = notebook_id
31
+    self.__user_id = user_id
32
+    self.__name = name
33
+    self.__description = description
34
+    self.__value = value
35
+
36
+  @staticmethod
37
+  def create( object_id, notebook_id = None, user_id = None, name = None, description = None, value = None ):
38
+    """
39
+    Convenience constructor for creating a new Tag.
40
+
41
+    @type object_id: unicode
42
+    @param object_id: id of the Tag
43
+    @type notebook_id: unicode or NoneType
44
+    @param notebook_id: id of the notebook whose namespace this tag is in, if any
45
+    @type user_id: unicode or NoneType
46
+    @param user_id: id of the user who most recently updated this tag, if any
47
+    @type name: unicode or NoneType
48
+    @param name: name of the tag (optional)
49
+    @type description: unicode or NoneType
50
+    @param description: brief description of the tag (optional)
51
+    @type value: unicode or NoneType
52
+    @param value: per-note or per-notebook value of the tag (optional)
53
+    @rtype: Tag
54
+    @return: newly constructed Tag
55
+    """
56
+    return Tag( object_id, notebook_id = notebook_id, user_id = user_id, name = name, description = description, value = value )
57
+
58
+  @staticmethod
59
+  def sql_load( object_id, revision = None ):
60
+    # Tags don't store old revisions
61
+    if revision:
62
+      raise NotImplementedError()
63
+
64
+    return \
65
+      """
66
+      select
67
+        tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description
68
+      from
69
+        tag
70
+      where
71
+        tag.id = %s;
72
+      """ % quote( object_id )
73
+
74
+  @staticmethod
75
+  def sql_load_by_name( name, notebook_id = None, user_id = None ):
76
+    if notebook_id:
77
+      notebook_id_clause = " and tag.notebook_id = %s" % quote( notebook_id )
78
+    else:
79
+      notebook_id_clause = ""
80
+
81
+    if user_id:
82
+      user_id_clause = " and tag.user_id = %s" % quote( user_id )
83
+    else:
84
+      user_id_clause = ""
85
+
86
+    return \
87
+      """
88
+      select
89
+        tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description
90
+      from
91
+        tag
92
+      where
93
+        tag.name = %s%s%s;
94
+      """ % ( quote( name ), notebook_id_clause, user_id_clause )
95
+
96
+  @staticmethod
97
+  def sql_id_exists( object_id, revision = None ):
98
+    if revision:
99
+      raise NotImplementedError()
100
+
101
+    return "select id from tag where id = %s;" % quote( object_id )
102
+
103
+  def sql_exists( self ):
104
+    return Tag.sql_id_exists( self.object_id )
105
+
106
+  def sql_create( self ):
107
+    return "insert into tag ( id, revision, notebook_id, user_id, name, description ) values ( %s, %s, %s, %s, %s, %s );" % \
108
+    ( quote( self.object_id ), quote( self.revision ), quote( self.__notebook_id ),
109
+      quote( self.__user_id ), quote( self.__name ), quote( self.__description ) )
110
+
111
+  def sql_update( self ):
112
+    return "update tag set revision = %s, notebook_id = %s, user_id = %s, name = %s, description = %s where id = %s;" % \
113
+    ( quote( self.revision ), quote( self.__notebook_id ), quote( self.__user_id ),
114
+      quote( self.__name ), quote( self.__description ), quote( self.object_id ) )
115
+
116
+  def sql_delete( self ):
117
+    return "delete from tag where id = %s;" % quote( self.object_id )
118
+
119
+  def to_dict( self ):
120
+    d = Persistent.to_dict( self )
121
+    d.update( dict(
122
+      notebook_id = self.__notebook_id,
123
+      user_id = self.__user_id,
124
+      name = self.__name,
125
+      description = self.__description,
126
+      value = self.__value,
127
+    ) )
128
+
129
+    return d
130
+
131
+  notebook_id = property( lambda self: self.__notebook_id )
132
+  user_id = property( lambda self: self.__user_id )
133
+  name = property( lambda self: self.__name )
134
+  description = property( lambda self: self.__description )
135
+  value = property( lambda self: self.__value )

+ 54
- 13
model/User.py View File

@@ -2,6 +2,7 @@ import sha
2 2
 import random
3 3
 from copy import copy
4 4
 from Persistent import Persistent, quote
5
+from Notebook import Notebook
5 6
 
6 7
 
7 8
 class User( Persistent ):
@@ -132,7 +133,8 @@ class User( Persistent ):
132 133
   def sql_load_by_email_address( email_address ):
133 134
     return "select * from luminotes_user_current where email_address = %s;" % quote( email_address )
134 135
 
135
-  def sql_load_notebooks( self, parents_only = False, undeleted_only = False, read_write = False ):
136
+  def sql_load_notebooks( self, parents_only = False, undeleted_only = False, read_write = False,
137
+                          tag_name = None, tag_value = None, notebook_id = None ):
136 138
     """
137 139
     Return a SQL string to load a list of the notebooks to which this user has access.
138 140
     """
@@ -151,28 +153,55 @@ class User( Persistent ):
151 153
     else:
152 154
       read_write_clause = ""
153 155
 
156
+    if tag_name:
157
+      tag_tables = ", tag_notebook, tag"
158
+      tag_clause = \
159
+        """
160
+         and tag_notebook.tag_id = tag.id and tag_notebook.user_id = %s and
161
+        tag_notebook.notebook_id = notebook_current.id and tag.name = %s
162
+        """ % ( quote( self.object_id ), quote( tag_name ) )
163
+
164
+      if tag_value:
165
+        tag_clause += " and tag_notebook.value = %s" % quote( tag_value )
166
+    else:
167
+      tag_tables = ""
168
+      tag_clause = ""
169
+
170
+    # useful for loading just a single notebook that the user has access to
171
+    if notebook_id:
172
+      notebook_id_clause = " and notebook_current.id = %s" % quote( notebook_id )
173
+    else:
174
+      notebook_id_clause = ""
175
+
154 176
     return \
155 177
       """
156 178
       select
157
-        notebook_current.*, user_notebook.read_write, user_notebook.owner, user_notebook.rank
179
+        notebook_current.*, user_notebook.read_write, user_notebook.owner, user_notebook.rank, user_notebook.own_notes_only
158 180
       from
159
-        user_notebook, notebook_current
181
+        user_notebook, notebook_current%s
160 182
       where
161
-        user_notebook.user_id = %s%s%s%s and
183
+        user_notebook.user_id = %s%s%s%s%s%s and
162 184
         user_notebook.notebook_id = notebook_current.id
163 185
       order by user_notebook.rank;
164
-      """ % ( quote( self.object_id ), parents_only_clause, undeleted_only_clause, read_write_clause )
186
+      """ % ( tag_tables, quote( self.object_id ), parents_only_clause, undeleted_only_clause,
187
+              read_write_clause, tag_clause, notebook_id_clause )
165 188
 
166
-  def sql_save_notebook( self, notebook_id, read_write = True, owner = True, rank = None ):
189
+  def sql_save_notebook( self, notebook_id, read_write = True, owner = True, rank = None, own_notes_only = False ):
167 190
     """
168 191
     Return a SQL string to save the id of a notebook to which this user has access.
169 192
     """
170 193
     if rank is None: rank = quote( None )
171 194
 
172 195
     return \
173
-      "insert into user_notebook ( user_id, notebook_id, read_write, owner, rank ) values " + \
174
-      "( %s, %s, %s, %s, %s );" % ( quote( self.object_id ), quote( notebook_id ), quote( read_write and 't' or 'f' ),
175
-                                quote( owner and 't' or 'f' ), rank )
196
+      "insert into user_notebook ( user_id, notebook_id, read_write, owner, rank, own_notes_only ) values " + \
197
+      "( %s, %s, %s, %s, %s, %s );" % (
198
+        quote( self.object_id ),
199
+        quote( notebook_id ),
200
+        quote( read_write and 't' or 'f' ),
201
+        quote( owner and 't' or 'f' ),
202
+        rank,
203
+        quote( own_notes_only and 't' or 'f' ),
204
+      )
176 205
 
177 206
   def sql_remove_notebook( self, notebook_id ):
178 207
     """
@@ -202,14 +231,26 @@ class User( Persistent ):
202 231
         "select user_id from user_notebook where user_id = %s and notebook_id = %s;" % \
203 232
         ( quote( self.object_id ), quote( notebook_id ) )
204 233
 
205
-  def sql_update_access( self, notebook_id, read_write = False, owner = False ):
234
+  def sql_update_access( self, notebook_id, read_write = Notebook.READ_ONLY, owner = False ):
206 235
     """
207 236
     Return a SQL string to update the user's notebook access to the given read_write and owner level.
208 237
     """
209 238
     return \
210
-      "update user_notebook set read_write = %s, owner = %s where user_id = %s and notebook_id = %s;" % \
211
-      ( quote( read_write and 't' or 'f' ), quote( owner and 't' or 'f' ), quote( self.object_id ),
212
-        quote( notebook_id ) )
239
+      "update user_notebook set read_write = %s, owner = %s, own_notes_only = %s where user_id = %s and notebook_id = %s;" % (
240
+        quote( ( read_write != Notebook.READ_ONLY ) and 't' or 'f' ),
241
+        quote( owner and 't' or 'f' ),
242
+        quote( ( read_write == Notebook.READ_WRITE_FOR_OWN_NOTES ) and 't' or 'f' ),
243
+        quote( self.object_id ),
244
+        quote( notebook_id ),
245
+      )
246
+
247
+  def sql_save_notebook_tag( self, notebook_id, tag_id, value = None ):
248
+    """
249
+    Return a SQL string to associate a tag with a notebook of this user.
250
+    """
251
+    return \
252
+      "insert into tag_notebook ( notebook_id, tag_id, value, user_id ) values " + \
253
+      "( %s, %s, %s, %s );" % ( quote( notebook_id ), quote( tag_id ), quote( value ), quote( self.object_id ) )
213 254
 
214 255
   def sql_update_notebook_rank( self, notebook_id, rank ):
215 256
     """

+ 30
- 0
model/delta/1.5.5.sql View File

@@ -0,0 +1,30 @@
1
+create table tag (
2
+  id text,
3
+  revision timestamp with time zone,
4
+  notebook_id text,
5
+  user_id text,
6
+  name text,
7
+  description text
8
+);
9
+ALTER TABLE ONLY tag ADD CONSTRAINT tag_pkey PRIMARY KEY (id);
10
+CREATE INDEX tag_notebook_id_index ON tag USING btree (notebook_id);
11
+CREATE INDEX tag_user_id_index ON tag USING btree (user_id);
12
+
13
+create table tag_notebook (
14
+  notebook_id text,
15
+  tag_id text,
16
+  value text,
17
+  user_id text
18
+);
19
+ALTER TABLE ONLY tag_notebook ADD CONSTRAINT tag_notebook_pkey PRIMARY KEY (user_id, notebook_id, tag_id);
20
+
21
+create table tag_note (
22
+  note_id text,
23
+  tag_id text,
24
+  value text
25
+);
26
+ALTER TABLE ONLY tag_note ADD CONSTRAINT tag_note_pkey PRIMARY KEY (note_id, tag_id);
27
+
28
+ALTER TABLE user_notebook ADD COLUMN own_notes_only boolean DEFAULT false;
29
+
30
+update user_notebook set rank = 0 from luminotes_user_current, notebook_current where user_notebook.user_id = luminotes_user_current.id and username = 'anonymous' and user_notebook.notebook_id = notebook_current.id and notebook_current.name = 'Luminotes'; 

+ 37
- 0
model/delta/1.5.5.sqlite View File

@@ -0,0 +1,37 @@
1
+create table tag (
2
+  id text,
3
+  revision timestamp with time zone,
4
+  notebook_id text,
5
+  user_id text,
6
+  name text,
7
+  description text
8
+);
9
+CREATE INDEX tag_pkey ON tag (id);
10
+CREATE INDEX tag_notebook_id_index ON tag (notebook_id);
11
+CREATE INDEX tag_user_id_index ON tag (user_id);
12
+
13
+create table tag_notebook (
14
+  notebook_id text,
15
+  tag_id text,
16
+  value text,
17
+  user_id text
18
+);
19
+CREATE INDEX tag_notebook_pkey ON tag_notebook (user_id, notebook_id, tag_id);
20
+
21
+create table tag_note (
22
+  note_id text,
23
+  tag_id text,
24
+  value text
25
+);
26
+CREATE INDEX tag_note_pkey ON tag_note (note_id, tag_id);
27
+
28
+CREATE INDEX file_pkey ON file (id);
29
+CREATE INDEX invite_pkey ON invite (id);
30
+CREATE INDEX luminotes_user_pkey ON luminotes_user (id, revision);
31
+CREATE INDEX note_pkey ON note (id, revision);
32
+CREATE INDEX notebook_pkey ON notebook (id, revision);
33
+CREATE INDEX password_reset_pkey ON password_reset (id);
34
+CREATE INDEX download_access_pkey ON download_access (id);
35
+CREATE INDEX user_notebook_pkey ON user_notebook (user_id, notebook_id);
36
+
37
+ALTER TABLE user_notebook ADD COLUMN own_notes_only boolean DEFAULT false;

+ 3
- 0
model/drop.sql View File

@@ -12,4 +12,7 @@ DROP TABLE user_notebook;
12 12
 DROP TABLE user_group;
13 13
 DROP TABLE invite;
14 14
 DROP TABLE file;
15
+DROP TABLE tag;
16
+DROP TABLE tag_notebook;
17
+DROP TABLE tag_note;
15 18
 DROP FUNCTION drop_html_tags( text ); 

+ 34
- 174
model/schema.sql View File

@@ -1,5 +1,5 @@
1 1
 --
2
+-- PostgreSQL database schema
2 3
 --
3 4
 
4 5
 SET client_encoding = 'UTF8';
@@ -12,22 +12,10 @@ SET default_tablespace = '';
12 12
 
13 13
 SET default_with_oids = false;
14 14
 
15
-
16
---
17
---
18
-
19 15
 CREATE FUNCTION drop_html_tags(text) RETURNS text
20 16
     AS $_$select regexp_replace( regexp_replace( $1, '</?(div|p|br|ul|ol|li|h3)( [^>]*?)?/?>', ' ', 'gi' ), '<[^>]+?>', '', 'g' );$_$
21 17
     LANGUAGE sql;
22
-
23
-
24 18
 ALTER FUNCTION public.drop_html_tags(text) OWNER TO luminotes;
25
-
26
---
27
---
28
-
29 19
 CREATE TABLE file (
30 20
     id text NOT NULL,
31 21
     revision timestamp with time zone,
@@ -37,14 +25,30 @@ CREATE TABLE file (
37 25
     size_bytes integer,
38 26
     content_type text
39 27
 );
40
-
41
-
42 28
 ALTER TABLE public.file OWNER TO luminotes;
29
+CREATE TABLE tag (
30
+    id text NOT NULL,
31
+    revision timestamp with time zone,
32
+    notebook_id text,
33
+    user_id text,
34
+    name text,
35
+    description text
36
+);
<