diff --git a/NEWS b/NEWS
index 23d61da..e70379d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,11 @@
+1.5.5:
+ * Improved speed of Luminotes Desktop by adding some database indices. This
+ will help in particular for larger notebooks with many notes.
+ * Added some code to automatically upgrade your database when upgrading to a
+ new Luminotes release. This applies to all Luminotes products.
+ * Added code to support Luminotes discussion forums.
+ * Laid some of the foundational groundwork for future tags support.
+
1.5.4: October 9, 2008
* Fixed a visual bug in which clicking up or down to reorder your notebooks
didn't display correctly.
diff --git a/UPGRADE b/UPGRADE
index cb55b2b..f536ef6 100644
--- a/UPGRADE
+++ b/UPGRADE
@@ -1,18 +1,44 @@
+When upgrading Luminotes, if you are using memcached, it is recommended
+that you restart memcached to clear your cache.
+
+
+Upgrading from Luminotes 1.5.0 or higher
+----------------------------------------
+
+If you're using Luminotes 1.5.0 or higher and you'd like to upgrade to a
+newer version, Luminotes will automatically upgrade your database when
+you start Luminotes after an upgrade. This means that all of your notes
+and notebooks created in an older versions of Luminotes will be included
+in the upgrade. You don't have to do a thing other than install the
+software for the new release, and then execute the following command:
+
+ export PYTHONPATH=.
+ python2.4 tools/updatedb.py
+
+
+Upgrading from Luminotes 1.0, 1.2, 1.3, or 1.4
+----------------------------------------------
+
+If you're using an older version of Luminotes (prior to 1.5.0) and you'd
+like to upgrade to a newer version, you'll have to perform database
+upgrades manually. Below are the intructions for doing so.
+
To upgrade the Luminotes database from an earlier version, manually apply each
relevant schema delta file within model/delta/
-For instance, if you were upgrading from version 5.0.1 to 5.0.4, you would
+For instance, if you are upgrading from version 1.3.12 to 1.5.0, you would
apply the following deltas in order:
- psql -U luminotes luminotes -f model/delta/5.0.2.sql
- psql -U luminotes luminotes -f model/delta/5.0.3.sql
- psql -U luminotes luminotes -f model/delta/5.0.4.sql
+ psql -U luminotes luminotes -f model/delta/1.3.14.sql
+ psql -U luminotes luminotes -f model/delta/1.4.0.sql
+ psql -U luminotes luminotes -f model/delta/1.5.0.sql
Any version which does not introduce a schema change does not have a
corresponding schema delta file.
-Sometimes I include comments within a schema delta file with additional
-manual steps you need to take.
+IMPORTANT: Even if you are upgrading past version 1.5.0 to a newer version,
+you should stop applying schema delta files after 1.5.0. This is because the
+Luminotes automatic schema upgrade process will pick up after that point.
After you've updated the schema, run the updatedb.py script:
diff --git a/config/Version.py b/config/Version.py
index 58b06c1..ee170a1 100644
--- a/config/Version.py
+++ b/config/Version.py
@@ -1 +1 @@
-VERSION = u"1.5.4"
+VERSION = u"1.5.5"
diff --git a/controller/Files.py b/controller/Files.py
index d9af4d4..9a0fb2a 100644
--- a/controller/Files.py
+++ b/controller/Files.py
@@ -290,7 +290,7 @@ class Files( object ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
# 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 ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
filename = db_file.filename.replace( '"', r"\"" )
@@ -432,7 +432,7 @@ class Files( object ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
cherrypy.response.headerMap[ u"Content-Type" ] = u"image/png"
@@ -491,7 +491,7 @@ class Files( object ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
cherrypy.response.headerMap[ u"Content-Type" ] = db_file.content_type
@@ -531,7 +531,7 @@ class Files( object ):
@return: rendered HTML page
@raise Access_error: the current user doesn't have access to the given notebook
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
+ if not self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id ):
raise Access_error()
file_id = self.__database.next_id( File )
@@ -565,7 +565,7 @@ class Files( object ):
@return: rendered HTML page
@raise Access_error: the current user doesn't have access to the given notebook
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
+ if not self.__users.load_notebook( user_id, notebook_id, read_write = True ):
raise Access_error()
file_id = self.__database.next_id( File )
@@ -622,7 +622,7 @@ class Files( object ):
current_uploads_lock.release()
user = self.__database.load( User, user_id )
- if not user or not self.__users.check_access( user_id, notebook_id, read_write = True ):
+ if not user or not self.__users.load_notebook( user_id, notebook_id, read_write = True ):
uploaded_file.delete()
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." )
@@ -739,7 +739,7 @@ class Files( object ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
user = self.__database.load( User, user_id )
@@ -778,7 +778,7 @@ class Files( object ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id, read_write = True ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id, read_write = True ):
raise Access_error()
self.__database.execute( db_file.sql_delete(), commit = False )
@@ -817,7 +817,7 @@ class Files( object ):
"""
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id, read_write = True ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id, read_write = True ):
raise Access_error()
db_file.filename = filename
@@ -919,7 +919,7 @@ class Files( object ):
db_file = self.__database.load( File, file_id )
- if not db_file or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if not db_file or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
parser = self.parse_csv( file_id )
diff --git a/controller/Forums.py b/controller/Forums.py
index 5eb668e..34b85ad 100644
--- a/controller/Forums.py
+++ b/controller/Forums.py
@@ -1,26 +1,35 @@
+import cherrypy
+from model.User import User
+from model.Notebook import Notebook
from Expose import expose
-from Validate import validate
+from Validate import validate, Valid_string
from Database import Valid_id, end_transaction
from Users import grab_user_id
+from Notebooks import Notebooks
from view.Forums_page import Forums_page
+from view.Forum_page import Forum_page
+from view.Main_page import Main_page
class Forums( object ):
"""
Controller for dealing with discussion forums, corresponding to the "/forums" URL.
"""
- def __init__( self, database, users ):
+ def __init__( self, database, notebooks, users ):
"""
Create a new Forums object.
@type database: controller.Database
@param database: database that forums are stored in
+ @type notebooks: controller.Users
+ @param notebooks: controller for all notebooks
@type users: controller.Users
@param users: controller for all users
@rtype: Forums
@return: newly constructed Forums
"""
self.__database = database
+ self.__notebooks = notebooks
self.__users = users
@expose( view = Forums_page )
@@ -32,6 +41,9 @@ class Forums( object ):
def index( self, user_id ):
"""
Provide the information necessary to display the listing of available forums (currently hard-coded).
+
+ @type user_id: unicode or NoneType
+ @param user_id: id of the current user
"""
result = self.__users.current( user_id )
parents = [ notebook for notebook in result[ u"notebooks" ] if notebook.trash_id and not notebook.deleted ]
@@ -41,3 +53,49 @@ class Forums( object ):
result[ "first_notebook" ] = None
return result
+
+ @expose( view = Forum_page )
+ @end_transaction
+ @grab_user_id
+ @validate(
+ forum_name = Valid_string( max = 100 ),
+ user_id = Valid_id( none_okay = True ),
+ )
+ def default( self, forum_name, user_id ):
+ """
+ Provide the information necessary to display the current threads within a forum.
+
+ @type forum_name: unicode
+ @param forum_name: name of the forum to display
+ @type user_id: unicode or NoneType
+ @param user_id: id of the current user
+ """
+ result = self.__users.current( user_id )
+ parents = [ notebook for notebook in result[ u"notebooks" ] if notebook.trash_id and not notebook.deleted ]
+ if len( parents ) > 0:
+ result[ "first_notebook" ] = parents[ 0 ]
+ else:
+ result[ "first_notebook" ] = None
+
+ anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
+ if anonymous is None:
+ raise Access_error()
+
+ # TODO: this needs to sort by either thread/note creation or modification date
+ threads = self.__database.select_many(
+ Notebook,
+ anonymous.sql_load_notebooks(
+ parents_only = False, undeleted_only = True, tag_name = u"forum", tag_value = forum_name
+ )
+ )
+
+ # if there are no matching threads, then this forum doesn't exist
+ if len( threads ) == 0:
+ raise cherrypy.NotFound
+
+ result[ "forum_name" ] = forum_name
+ result[ "threads" ] = threads
+ return result
+
+ # threads() is just an alias for Notebooks.default()
+ threads = Notebooks.default
diff --git a/controller/Notebooks.py b/controller/Notebooks.py
index e7dae5b..6c421d2 100644
--- a/controller/Notebooks.py
+++ b/controller/Notebooks.py
@@ -135,7 +135,7 @@ class Notebooks( object ):
]
if len( result[ u"notebooks" ] ) == 0:
raise Access_error()
- result[ u"notebooks" ][ 0 ].read_write = False
+ result[ u"notebooks" ][ 0 ].read_write = Notebook.READ_ONLY
result[ u"notebooks" ][ 0 ].owner = False
elif preview in ( u"owner", u"default", None ):
read_write = True
@@ -148,9 +148,8 @@ class Notebooks( object ):
if revision:
result[ "note_read_write" ] = False
- notebook = self.__database.load( Notebook, notebook_id )
- if not notebook:
- raise Access_error()
+ notebook = result[ u"notebook" ]
+
if notebook.name != u"Luminotes":
result[ "recent_notes" ] = self.__database.select_many( Note, notebook.sql_load_notes( start = 0, count = 10 ) )
@@ -181,9 +180,11 @@ class Notebooks( object ):
@type previous_revision: unicode or NoneType
@param previous_revision: older revision timestamp to diff with the given revision (optional)
@type read_write: bool or NoneType
- @param read_write: whether the notebook should be returned as read-write (optional, defaults to True)
+ @param read_write: whether the notebook should be returned as read-write (optional, defaults to True).
+ this can only lower access, not elevate it
@type owner: bool or NoneType
- @param owner: whether the notebook should be returned as owner-level access (optional, defaults to True)
+ @param owner: whether the notebook should be returned as owner-level access (optional, defaults to True).
+ this can only lower access, not elevate it
@type user_id: unicode or NoneType
@param user_id: id of current logged-in user (if any)
@rtype: dict
@@ -197,23 +198,16 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook or note
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id )
if notebook is None:
raise Access_error()
if read_write is False:
- notebook.read_write = False
- elif not self.__users.check_access( user_id, notebook_id, read_write = True ):
- notebook.read_write = False
+ notebook.read_write = Notebook.READ_ONLY
if owner is False:
notebook.owner = False
- elif not self.__users.check_access( user_id, notebook_id, owner = True ):
- notebook.owner = False
if note_id:
note = self.__database.load( Note, note_id, revision )
@@ -234,7 +228,7 @@ class Notebooks( object ):
startup_notes = self.__database.select_many( Note, notebook.sql_load_startup_notes() )
total_notes_count = self.__database.select_one( int, notebook.sql_count_notes(), use_cache = True )
- if self.__users.check_access( user_id, notebook_id, owner = True ):
+ if self.__users.load_notebook( user_id, notebook_id, owner = True ):
invites = self.__database.select_many( Invite, Invite.sql_load_notebook_invites( notebook_id ) )
else:
invites = []
@@ -350,7 +344,9 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook or note
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
+ notebook = self.__users.load_notebook( user_id, notebook_id )
+
+ if not notebook:
raise Access_error()
note = self.__database.load( Note, note_id, revision )
@@ -362,8 +358,7 @@ class Notebooks( object ):
)
if note and note.notebook_id != notebook_id:
- notebook = self.__database.load( Notebook, notebook_id )
- if notebook and note.notebook_id == notebook.trash_id:
+ if note.notebook_id == notebook.trash_id:
if revision:
return dict(
note = summarize and self.summarize_note( note ) or note,
@@ -414,15 +409,12 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
+ notebook = self.__users.load_notebook( user_id, notebook_id )
+
+ if not notebook:
raise Access_error()
- notebook = self.__database.load( Notebook, notebook_id )
-
- if notebook is None:
- note = None
- else:
- note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
+ note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
return dict(
note = summarize and self.summarize_note( note ) or note,
@@ -520,15 +512,12 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
+ notebook = self.__users.load_notebook( user_id, notebook_id )
+
+ if not notebook:
raise Access_error()
- notebook = self.__database.load( Notebook, notebook_id )
-
- if notebook is None:
- note = None
- else:
- note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
+ note = self.__database.select_one( Note, notebook.sql_load_note_by_title( note_title ) )
return dict(
note_id = note and note.object_id or None,
@@ -558,7 +547,9 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook or note
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
+ notebook = self.__users.load_notebook( user_id, notebook_id )
+
+ if not notebook:
raise Access_error()
note = self.__database.load( Note, note_id )
@@ -570,8 +561,7 @@ class Notebooks( object ):
)
if note.notebook_id != notebook_id:
- notebook = self.__database.load( Notebook, notebook_id )
- if notebook and note.notebook_id == notebook.trash_id:
+ if note.notebook_id == notebook.trash_id:
return dict(
revisions = None,
)
@@ -610,10 +600,8 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook or note
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
+ notebook = self.__users.load_notebook( user_id, notebook_id )
- notebook = self.__database.load( Notebook, notebook_id )
if not notebook:
raise Access_error()
@@ -696,17 +684,19 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
- raise Access_error()
-
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id )
user = self.__database.load( User, user_id )
- notebook = self.__database.load( Notebook, notebook_id )
if not user or not notebook:
- raise Access_error()
+ raise Access_error();
note = self.__database.load( Note, note_id )
+ # if the user has read-write access only to their own notes in this notebook, force the startup
+ # flag to be True for this note
+ if notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES:
+ startup = True
+
# check whether the provided note contents have been changed since the previous revision
def update_note( current_notebook, old_note, startup, user ):
# the note hasn't been changed, so bail without updating it
@@ -805,11 +795,8 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
- raise Access_error()
-
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True )
user = self.__database.load( User, user_id )
- notebook = self.__database.load( Notebook, notebook_id )
if not user or not notebook:
raise Access_error()
@@ -819,6 +806,9 @@ class Notebooks( object ):
if not note:
raise Access_error()
+ if not self.__users.load_notebook( user_id, note.notebook_id, read_write = True, note_id = note.object_id ):
+ raise Access_error()
+
# check whether the provided note contents have been changed since the previous revision
def update_note( current_notebook, old_note, user ):
# if the revision to revert to is already the newest revision, bail without updating the note
@@ -895,10 +885,7 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id )
if not notebook:
raise Access_error()
@@ -949,10 +936,7 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, note_id = note_id )
if not notebook:
raise Access_error()
@@ -1005,12 +989,9 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id, read_write = True ):
- raise Access_error()
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True )
- notebook = self.__database.load( Notebook, notebook_id )
-
- if not notebook:
+ if not notebook or notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES:
raise Access_error()
notes = self.__database.select_many( Note, notebook.sql_load_notes() )
@@ -1063,7 +1044,9 @@ class Notebooks( object ):
@raise Validation_error: one of the arguments is invalid
@raise Search_error: the provided search_text is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
+ notebook = self.__users.load_notebook( user_id, notebook_id )
+
+ if not notebook:
raise Access_error()
MAX_SEARCH_TEXT_LENGTH = 256
@@ -1112,14 +1095,19 @@ class Notebooks( object ):
@raise Validation_error: one of the arguments is invalid
@raise Search_error: the provided search_text is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
# if the anonymous user has access to the given notebook, then run the search as the anonymous
# user instead of the given user id
- if self.__users.check_access( user_id = None, notebook_id = notebook_id ) is True:
- anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
+ anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
+ if not anonymous:
+ raise Access_error()
+
+ notebook = self.__users.load_notebook( anonymous.object_id, notebook_id )
+ if notebook:
user_id = anonymous.object_id
+ else:
+ notebook = self.__users.load_notebook( user_id, notebook_id )
+ if not notebook:
+ raise Access_error()
MAX_SEARCH_TEXT_LENGTH = 256
if len( search_text ) > MAX_SEARCH_TEXT_LENGTH:
@@ -1162,10 +1150,7 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id )
if not notebook:
raise Access_error()
@@ -1197,10 +1182,7 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id )
if not notebook:
raise Access_error()
@@ -1234,10 +1216,7 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id )
if not notebook:
raise Access_error()
@@ -1346,13 +1325,10 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
user = self.__database.load( User, user_id )
- if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
- raise Access_error()
- notebook = self.__database.load( Notebook, notebook_id )
-
- if not notebook:
+ if not user or not notebook:
raise Access_error()
# prevent renaming of the trash notebook to anything
@@ -1399,14 +1375,10 @@ class Notebooks( object ):
if user_id is None:
raise Access_error()
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
user = self.__database.load( User, user_id )
- if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
-
- if not notebook:
+ if not user or not notebook:
raise Access_error()
# prevent deletion of a trash notebook directly
@@ -1454,14 +1426,10 @@ class Notebooks( object ):
if user_id is None:
raise Access_error()
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
user = self.__database.load( User, user_id )
- if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
-
- if not notebook:
+ if not user or not notebook:
raise Access_error()
# prevent deletion of a trash notebook directly
@@ -1498,10 +1466,7 @@ class Notebooks( object ):
if user_id is None:
raise Access_error()
- if not self.__users.check_access( user_id, notebook_id, read_write = True, owner = True ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id, read_write = True, owner = True )
if not notebook:
raise Access_error()
@@ -1537,11 +1502,10 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
+ notebook = self.__users.load_notebook( user_id, notebook_id )
user = self.__database.load( User, user_id )
- if not user:
+
+ if not user or not notebook:
raise Access_error()
# load the notebooks to which this user has access
@@ -1609,11 +1573,10 @@ class Notebooks( object ):
@raise Access_error: the current user doesn't have access to the given notebook
@raise Validation_error: one of the arguments is invalid
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
+ notebook = self.__users.load_notebook( user_id, notebook_id )
user = self.__database.load( User, user_id )
- if not user:
+
+ if not user or not notebook:
raise Access_error()
# load the notebooks to which this user has access
@@ -1676,7 +1639,6 @@ class Notebooks( object ):
Provide the information necessary to display a notebook's recent updated/created notes, in
reverse chronological order by update time.
-
@type notebook_id: unicode
@param notebook_id: id of the notebook containing the notes
@type start: unicode or NoneType
@@ -1689,10 +1651,7 @@ class Notebooks( object ):
@return: { 'notes': recent_notes_list }
@raise Access_error: the current user doesn't have access to the given notebook or note
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id )
if notebook is None:
raise Access_error()
@@ -1720,10 +1679,7 @@ class Notebooks( object ):
@return: data for Main_page() constructor
@raise Access_error: the current user doesn't have access to the given notebook or note
"""
- if not self.__users.check_access( user_id, notebook_id ):
- raise Access_error()
-
- notebook = self.__database.load( Notebook, notebook_id )
+ notebook = self.__users.load_notebook( user_id, notebook_id )
if notebook is None:
raise Access_error()
@@ -1798,7 +1754,7 @@ class Notebooks( object ):
raise Access_error()
db_file = self.__database.load( File, file_id )
- if db_file is None or not self.__users.check_access( user_id, db_file.notebook_id ):
+ if db_file is None or not self.__users.load_notebook( user_id, db_file.notebook_id ):
raise Access_error()
# if the file has a "note_id" header column, record its index
diff --git a/controller/Root.py b/controller/Root.py
index 220ee7b..a85591a 100644
--- a/controller/Root.py
+++ b/controller/Root.py
@@ -58,7 +58,7 @@ class Root( object ):
settings[ u"global" ].get( u"luminotes.download_products", [] ),
)
self.__notebooks = Notebooks( database, self.__users, self.__files, settings[ u"global" ].get( u"luminotes.https_url", u"" ) )
- self.__forums = Forums( database, self.__users )
+ self.__forums = Forums( database, self.__notebooks, self.__users )
self.__suppress_exceptions = suppress_exceptions # used for unit tests
@expose( Main_page )
@@ -486,4 +486,4 @@ class Root( object ):
users = property( lambda self: self.__users )
groups = property( lambda self: self.__groups )
files = property( lambda self: self.__files )
-# forums = property( lambda self: self.__forums )
+ forums = property( lambda self: self.__forums )
diff --git a/controller/Users.py b/controller/Users.py
index 5ff3075..ebc4557 100644
--- a/controller/Users.py
+++ b/controller/Users.py
@@ -702,34 +702,62 @@ class Users( object ):
return user
- def check_access( self, user_id, notebook_id, read_write = False, owner = False ):
+ def load_notebook( self, user_id, notebook_id, read_write = False, owner = False, note_id = None ):
"""
- Determine whether the given user has access to the given notebook.
+ Determine whether the given user has access to the given notebook, and if so, return that
+ notebook.
+
+ If the notebook.read_write member is READ_WRITE_FOR_OWN_NOTES, and a particular note_id is
+ given, then make sure that the given note_id is one of the user's own notes.
@type user_id: unicode
@param user_id: id of user whose access to check
@type notebook_id: unicode
@param notebook_id: id of notebook to check access for
- @type read_write: bool
- @param read_write: True if read-write access is being checked, False if read-only access (defaults to False)
+ @type read_write: boolean
+ @param read_write: True if the notebook must be READ_WRITE or READ_WRITE_FOR_OWN_NOTES,
+ False if read-write access is not to be checked (defaults to False)
@type owner: bool
@param owner: True if owner-level access is being checked (defaults to False)
- @rtype: bool
- @return: True if the user has access
+ @type note_id: unicode
+ @param note_id: id of the note in the given notebook that the user is trying to access.
+ if the notebook is READ_WRITE_FOR_OWN_NOTES, then the given note is checked
+ to make sure its user_id is the same as the given user_id. for READ_WRITE
+ and READ_ONLY notebooks, this note_id parameter is ignored
+ @rtype: Notebook or NoneType
+ @return: the loaded notebook if the user has access to it, None otherwise
"""
anonymous = self.__database.select_one( User, User.sql_load_by_username( u"anonymous" ), use_cache = True )
+ notebook = self.__database.select_one( Notebook, anonymous.sql_load_notebooks( notebook_id = notebook_id ) )
- if self.__database.select_one( bool, anonymous.sql_has_access( notebook_id, read_write, owner ) ):
- return True
-
- if user_id:
- # check if the given user has access to this notebook
+ if not notebook and user_id:
user = self.__database.load( User, user_id )
+ if not user:
+ return None
- if user and self.__database.select_one( bool, user.sql_has_access( notebook_id, read_write, owner ) ):
- return True
+ notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( notebook_id = notebook_id ) )
- return False
+ # if the user has no access to this notebook, bail
+ if notebook is None:
+ return None
+
+ if read_write and notebook.read_write == Notebook.READ_ONLY:
+ return None
+
+ if owner and not notebook.owner:
+ return None
+
+ # if a particular note_id is given, and the notebook is READ_WRITE_FOR_OWN_NOTES, then check
+ # that the user is associated with that note
+ if note_id and notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES:
+ note = self.__database.load( Note, note_id )
+ if not note:
+ return None
+
+ if user_id != note.user_id or notebook_id != note.notebook_id:
+ return None
+
+ return notebook
def check_group( self, user_id, group_id, admin = False ):
"""
@@ -1006,7 +1034,9 @@ class Users( object ):
if len( email_addresses ) > 5000:
raise Invite_error( u"Please enter fewer email addresses." )
- if not self.check_access( user_id, notebook_id, read_write = True, owner = True ):
+ notebook = self.load_notebook( user_id, notebook_id, read_write = True, owner = True )
+
+ if not notebook:
raise Access_error()
# except for viewer-only invites, this feature requires a rate plan above basic
@@ -1027,10 +1057,6 @@ class Users( object ):
else:
raise Access_error()
- notebook = self.__database.load( Notebook, notebook_id )
- if notebook is None:
- raise Access_error()
-
# parse email_addresses string into individual email addresses
email_addresses_list = set()
for piece in WHITESPACE_OR_COMMA_PATTERN.split( email_addresses ):
@@ -1136,12 +1162,13 @@ class Users( object ):
@raise Validation_error: one of the arguments is invalid
@raise Access_error: user_id doesn't have owner-level notebook access to revoke an invite
"""
- if not self.check_access( user_id, notebook_id, read_write = True, owner = True ):
+ notebook = self.load_notebook( user_id, notebook_id, read_write = True, owner = True )
+
+ if not notebook:
raise Access_error()
invite = self.__database.load( Invite, invite_id )
- notebook = self.__database.load( Notebook, notebook_id )
- if not notebook or not invite or not invite.email_address or invite.notebook_id != notebook_id:
+ if not invite or not invite.email_address or invite.notebook_id != notebook_id:
raise Access_error()
self.__database.execute(
diff --git a/controller/test/Test_database.py b/controller/test/Test_database.py
index ff514a1..880a8bf 100644
--- a/controller/test/Test_database.py
+++ b/controller/test/Test_database.py
@@ -10,7 +10,7 @@ from controller.Database import Database, Connection_wrapper
class Test_database( object ):
def setUp( self ):
- # make an in-memory sqlite database to use in place of PostgreSQL during testing
+ # make an in-memory sqlite database to use during testing
self.connection = Connection_wrapper( sqlite.connect( ":memory:", detect_types = sqlite.PARSE_DECLTYPES, check_same_thread = False ) )
self.cache = Stub_cache()
cursor = self.connection.cursor()
diff --git a/controller/test/Test_notebooks.py b/controller/test/Test_notebooks.py
index 88a5eeb..2aa59bc 100644
--- a/controller/test/Test_notebooks.py
+++ b/controller/test/Test_notebooks.py
@@ -175,13 +175,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 2 ].read_write == True
+ assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 2 ].owner == True
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == True
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -211,13 +211,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 1
assert result.get( u"notebooks" )[ 0 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 0 ].read_write == False
+ assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_ONLY
assert result.get( u"notebooks" )[ 0 ].owner == False
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == False
+ assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
assert result.get( u"notebook" ).owner == False
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -247,13 +247,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 1
assert result.get( u"notebooks" )[ 0 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 0 ].read_write == True
+ assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 0 ].owner == False
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == False
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -283,13 +283,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 2 ].read_write == True
+ assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 2 ].owner == True
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == True
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -319,13 +319,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 1
assert result.get( u"notebooks" )[ 0 ].object_id == self.anon_notebook.object_id
- assert result.get( u"notebooks" )[ 0 ].read_write == False
+ assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_ONLY
assert result.get( u"notebooks" )[ 0 ].owner == False
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.anon_notebook.object_id
- assert result.get( u"notebook" ).read_write == False
+ assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
assert result.get( u"notebook" ).owner == False
assert len( result.get( u"startup_notes" ) ) == 0
assert result[ "total_notes_count" ] == 0
@@ -351,13 +351,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 1
assert result.get( u"notebooks" )[ 0 ].object_id == self.anon_notebook.object_id
- assert result.get( u"notebooks" )[ 0 ].read_write == False
+ assert result.get( u"notebooks" )[ 0 ].read_write == Notebook.READ_ONLY
assert result.get( u"notebooks" )[ 0 ].owner == False
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.anon_notebook.object_id
- assert result.get( u"notebook" ).read_write == False
+ assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
assert result.get( u"notebook" ).owner == False
assert len( result.get( u"startup_notes" ) ) == 0
assert result[ "total_notes_count" ] == 0
@@ -380,14 +380,18 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
- assert result.get( u"notebooks" )[ 1 ].object_id == self.anon_notebook.object_id
- assert result.get( u"notebooks" )[ 1 ].read_write == False
- assert result.get( u"notebooks" )[ 1 ].owner == False
+ notebook = result[ u"notebooks" ][ 0 ]
+ if notebook.name == u"trash":
+ notebook = result[ u"notebooks" ][ 1 ]
+
+ assert notebook.object_id == self.anon_notebook.object_id
+ assert notebook.read_write == Notebook.READ_ONLY
+ assert notebook.owner == False
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.anon_notebook.object_id
- assert result.get( u"notebook" ).read_write == False
+ assert result.get( u"notebook" ).read_write == Notebook.READ_ONLY
assert result.get( u"notebook" ).owner == False
assert len( result.get( u"startup_notes" ) ) == 0
assert result[ "total_notes_count" ] == 0
@@ -468,13 +472,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 2 ].read_write == True
+ assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 2 ].owner == True
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == True
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -511,13 +515,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 2 ].read_write == True
+ assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 2 ].owner == True
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == True
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -560,13 +564,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 2 ].read_write == True
+ assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 2 ].owner == True
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == True
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -602,13 +606,13 @@ class Test_notebooks( Test_controller ):
assert result.get( u"user" ).object_id == self.user.object_id
assert len( result.get( u"notebooks" ) ) == 3
assert result.get( u"notebooks" )[ 2 ].object_id == self.notebook.object_id
- assert result.get( u"notebooks" )[ 2 ].read_write == True
+ assert result.get( u"notebooks" )[ 2 ].read_write == Notebook.READ_WRITE
assert result.get( u"notebooks" )[ 2 ].owner == True
assert result.get( u"login_url" ) is None
assert result.get( u"logout_url" )
assert result.get( u"rate_plan" )
assert result.get( u"notebook" ).object_id == self.notebook.object_id
- assert result.get( u"notebook" ).read_write == True
+ assert result.get( u"notebook" ).read_write == Notebook.READ_WRITE
assert result.get( u"notebook" ).owner == True
assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2
@@ -644,7 +648,7 @@ class Test_notebooks( Test_controller ):
assert invite.object_id == self.invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -669,7 +673,7 @@ class Test_notebooks( Test_controller ):
assert invite.object_id == self.invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -694,7 +698,7 @@ class Test_notebooks( Test_controller ):
assert invite.object_id == self.invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == False
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -718,7 +722,7 @@ class Test_notebooks( Test_controller ):
assert invite.object_id == self.invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -751,7 +755,7 @@ class Test_notebooks( Test_controller ):
assert invite.object_id == self.invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -790,7 +794,7 @@ class Test_notebooks( Test_controller ):
assert invite.object_id == self.invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -830,7 +834,7 @@ class Test_notebooks( Test_controller ):
assert invites[ 1 ].object_id == invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -861,7 +865,7 @@ class Test_notebooks( Test_controller ):
assert invites[ 1 ].object_id == invite.object_id
assert notebook.object_id == self.notebook.object_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id
@@ -901,7 +905,7 @@ class Test_notebooks( Test_controller ):
assert result[ "invites" ] == []
assert notebook.object_id == self.anon_notebook.object_id
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert len( startup_notes ) == 0
user = self.database.load( User, self.user.object_id )
@@ -1727,6 +1731,72 @@ class Test_notebooks( Test_controller ):
def test_save_startup_note( self ):
self.test_save_note( startup = True )
+ def test_save_note_in_notebook_with_read_write_for_own_notes( self ):
+ self.login()
+
+ self.database.execute( self.user.sql_update_access(
+ self.notebook.object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = True,
+ ) )
+
+ previous_revision = self.note.revision
+ new_note_contents = u"
new title
new blah"
+ result = self.http_post( "/notebooks/save_note/", dict(
+ notebook_id = self.notebook.object_id,
+ note_id = self.note.object_id,
+ contents = new_note_contents,
+ startup = False,
+ previous_revision = previous_revision,
+ ), session_id = self.session_id )
+
+ assert result[ "new_revision" ]
+ assert result[ "new_revision" ].revision != previous_revision
+ assert result[ "new_revision" ].user_id == self.user.object_id
+ assert result[ "new_revision" ].username == self.username
+ current_revision = result[ "new_revision" ].revision
+ assert result[ "previous_revision" ].revision == previous_revision
+ assert result[ "previous_revision" ].user_id == self.user.object_id
+ assert result[ "previous_revision" ].username == self.username
+
+ # make sure the old title can no longer be loaded
+ result = self.http_post( "/notebooks/load_note_by_title/", dict(
+ notebook_id = self.notebook.object_id,
+ note_title = "my title",
+ ), session_id = self.session_id )
+
+ note = result[ "note" ]
+ assert note == None
+
+ # make sure the new title is now loadable
+ result = self.http_post( "/notebooks/load_note_by_title/", dict(
+ notebook_id = self.notebook.object_id,
+ note_title = "new title",
+ ), session_id = self.session_id )
+
+ note = result[ "note" ]
+
+ assert note.object_id == self.note.object_id
+ assert note.title == "new title"
+ assert note.contents == new_note_contents
+ assert note.startup == True # startup is forced to True in READ_WRITE_FOR_OWN_NOTES notebook
+ assert note.user_id == self.user.object_id
+ assert note.rank == 0
+
+ # make sure that the correct revisions are returned and are in chronological order
+ result = self.http_post( "/notebooks/load_note_revisions/", dict(
+ notebook_id = self.notebook.object_id,
+ note_id = self.note.object_id,
+ ), session_id = self.session_id )
+
+ revisions = result[ "revisions" ]
+ assert revisions != None
+ assert len( revisions ) == 3
+ assert revisions[ 1 ].revision == previous_revision
+ assert revisions[ 1 ].user_id == self.user.object_id
+ assert revisions[ 1 ].username == self.username
+ assert revisions[ 2 ].revision == current_revision
+ assert revisions[ 2 ].user_id == self.user.object_id
+ assert revisions[ 2 ].username == self.username
+
def test_save_note_by_different_user( self, startup = False ):
self.login2()
@@ -1796,6 +1866,27 @@ class Test_notebooks( Test_controller ):
assert revisions[ 2 ].user_id == self.user2.object_id
assert revisions[ 2 ].username == self.username2
+ def test_save_note_by_different_user_with_notebook_read_write_for_own_notes( self ):
+ self.login2()
+
+ self.database.execute( self.user2.sql_update_access(
+ self.notebook.object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = True,
+ ) )
+
+ previous_revision = self.note.revision
+ new_note_contents = u"new title
new blah"
+ result = self.http_post( "/notebooks/save_note/", dict(
+ notebook_id = self.notebook.object_id,
+ note_id = self.note.object_id,
+ contents = new_note_contents,
+ startup = False,
+ previous_revision = previous_revision,
+ ), session_id = self.session_id )
+
+ assert result.get( "error" )
+ user = self.database.load( User, self.user.object_id )
+ assert user.storage_bytes == 0
+
def test_save_note_without_login( self, startup = False ):
# save over an existing note supplying new contents and a new title
previous_revision = self.note.revision
@@ -3688,7 +3779,7 @@ class Test_notebooks( Test_controller ):
assert isinstance( notebook, Notebook )
assert notebook.object_id == new_notebook_id
assert notebook.name == u"new notebook"
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.trash_id
@@ -3715,7 +3806,7 @@ class Test_notebooks( Test_controller ):
assert result[ "invites" ] == []
assert notebook.object_id == new_notebook_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
def test_create_without_login( self ):
@@ -3810,7 +3901,7 @@ class Test_notebooks( Test_controller ):
assert isinstance( notebook, Notebook )
assert notebook.object_id == remaining_notebook_id
assert notebook.name == u"my notebook"
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.trash_id
assert notebook.user_id == self.user.object_id
@@ -3864,7 +3955,7 @@ class Test_notebooks( Test_controller ):
assert isinstance( notebook, Notebook )
assert notebook.object_id == remaining_notebook_id
assert notebook.name == u"my notebook"
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.trash_id
assert notebook.user_id == self.user.object_id
@@ -4009,7 +4100,7 @@ class Test_notebooks( Test_controller ):
assert isinstance( notebook, Notebook )
assert notebook.object_id == notebook_id
assert notebook.name == self.notebook.name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.trash_id
assert notebook.user_id == self.user.object_id
@@ -4069,7 +4160,7 @@ class Test_notebooks( Test_controller ):
assert isinstance( notebook, Notebook )
assert notebook.object_id == notebook_id
assert notebook.name == self.notebook.name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.trash_id
assert notebook.user_id == self.user.object_id
@@ -4511,7 +4602,7 @@ class Test_notebooks( Test_controller ):
assert notebook.name == u"imported notebook"
assert notebook.trash_id
- assert notebook.read_write is True
+ assert notebook.read_write is Notebook.READ_WRITE
assert notebook.owner is True
assert notebook.deleted is False
assert notebook.user_id == self.user.object_id
diff --git a/controller/test/Test_root.py b/controller/test/Test_root.py
index 81740f7..aa3dc50 100644
--- a/controller/test/Test_root.py
+++ b/controller/test/Test_root.py
@@ -62,10 +62,10 @@ class Test_root( Test_controller ):
self.anonymous = User.create( self.database.next_id( User ), u"anonymous" )
self.database.save( self.anonymous )
- self.database.execute( self.anonymous.sql_save_notebook( self.anon_notebook.object_id, read_write = False, owner = False ) )
- self.database.execute( self.anonymous.sql_save_notebook( self.blog_notebook.object_id, read_write = False, owner = False ) )
- self.database.execute( self.anonymous.sql_save_notebook( self.guide_notebook.object_id, read_write = False, owner = False ) )
- self.database.execute( self.anonymous.sql_save_notebook( self.privacy_notebook.object_id, read_write = False, owner = False ) )
+ self.database.execute( self.anonymous.sql_save_notebook( self.anon_notebook.object_id, read_write = False, owner = False, rank = 0 ) )
+ self.database.execute( self.anonymous.sql_save_notebook( self.blog_notebook.object_id, read_write = False, owner = False, rank = 1 ) )
+ self.database.execute( self.anonymous.sql_save_notebook( self.guide_notebook.object_id, read_write = False, owner = False, rank = 2 ) )
+ self.database.execute( self.anonymous.sql_save_notebook( self.privacy_notebook.object_id, read_write = False, owner = False, rank = 3 ) )
def test_index( self ):
result = self.http_get( "/" )
@@ -429,7 +429,7 @@ class Test_root( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.anon_notebook.object_id ][ 0 ]
assert notebook.object_id == self.anon_notebook.object_id
assert notebook.name == self.anon_notebook.name
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
rate_plan = result[ u"rate_plan" ]
@@ -451,7 +451,7 @@ class Test_root( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebook.object_id ][ 0 ]
assert notebook.object_id == self.notebook.object_id
assert notebook.name == self.notebook.name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
rate_plan = result[ u"rate_plan" ]
diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py
index 2d07fe5..bbe2c00 100644
--- a/controller/test/Test_users.py
+++ b/controller/test/Test_users.py
@@ -62,21 +62,23 @@ class Test_users( Test_controller ):
self.database.save( self.notebooks[ 0 ] )
self.database.save( self.notebooks[ 1 ] )
+ self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
+ self.database.save( self.user, commit = False )
+
self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"anon notebook" )
self.database.save( self.anon_notebook )
self.startup_note = Note.create(
self.database.next_id( Note ), u"login
",
notebook_id = self.anon_notebook.object_id, startup = True,
+ user_id = self.user.object_id,
)
- self.database.save( self.startup_note )
+ self.database.save( self.startup_note, commit = False )
self.group = Group.create( self.database.next_id( Group ), u"my group" )
self.database.save( self.group, commit = False )
self.group2 = Group.create( self.database.next_id( Group ), u"other group" )
self.database.save( self.group2, commit = False )
- self.user = User.create( self.database.next_id( User ), self.username, self.password, self.email_address )
- self.database.save( self.user, commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id1, read_write = True, owner = True, rank = 0 ), commit = False )
self.database.execute( self.user.sql_save_notebook( trash_id1, read_write = True, owner = True ), commit = False )
self.database.execute( self.user.sql_save_notebook( notebook_id2, read_write = True, owner = True, rank = 1 ), commit = False )
@@ -180,7 +182,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == None
@@ -189,7 +191,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"my notebook"
assert notebook.trash_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -198,7 +200,7 @@ class Test_users( Test_controller ):
assert notebook.revision == self.anon_notebook.revision
assert notebook.name == self.anon_notebook.name
assert notebook.trash_id == None
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert notebook.rank == None
@@ -250,8 +252,8 @@ class Test_users( Test_controller ):
assert result[ u"user" ].username == self.new_username
assert result[ u"user" ].email_address == self.new_email_address
- assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].object_id )
- assert cherrypy.root.users.check_access( user.object_id, self.notebooks[ 0 ].trash_id )
+ assert cherrypy.root.users.load_notebook( user.object_id, self.notebooks[ 0 ].object_id )
+ assert cherrypy.root.users.load_notebook( user.object_id, self.notebooks[ 0 ].trash_id )
# the notebook that the user was invited to should be in the list of returned notebooks
notebooks = dict( [ ( notebook.object_id, notebook ) for notebook in result[ u"notebooks" ] ] )
@@ -261,7 +263,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == self.notebooks[ 0 ].name
assert notebook.trash_id
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert notebook.rank == 1
@@ -269,7 +271,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert notebook.rank == None
@@ -277,7 +279,7 @@ class Test_users( Test_controller ):
assert notebook.revision == self.anon_notebook.revision
assert notebook.name == self.anon_notebook.name
assert notebook.trash_id == None
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert notebook.rank == None
@@ -317,7 +319,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == None
@@ -326,7 +328,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"my notebook"
assert notebook.trash_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -335,7 +337,7 @@ class Test_users( Test_controller ):
assert notebook.revision == self.anon_notebook.revision
assert notebook.name == self.anon_notebook.name
assert notebook.trash_id == None
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert notebook.rank == None
@@ -639,7 +641,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"trash"
assert notebook.trash_id == None
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == None
@@ -648,7 +650,7 @@ class Test_users( Test_controller ):
assert notebook.revision
assert notebook.name == u"my notebook"
assert notebook.trash_id
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -657,7 +659,7 @@ class Test_users( Test_controller ):
assert notebook.revision == self.anon_notebook.revision
assert notebook.name == self.anon_notebook.name
assert notebook.trash_id == None
- assert notebook.read_write == False
+ assert notebook.read_write == Notebook.READ_ONLY
assert notebook.owner == False
assert notebook.rank == None
@@ -740,27 +742,27 @@ class Test_users( Test_controller ):
assert len( result[ u"notebooks" ] ) == 5
assert result[ u"notebooks" ][ 0 ].object_id
assert result[ u"notebooks" ][ 0 ].name == u"trash"
- assert result[ u"notebooks" ][ 0 ].read_write == True
+ assert result[ u"notebooks" ][ 0 ].read_write == Notebook.READ_WRITE
assert result[ u"notebooks" ][ 0 ].owner == True
assert result[ u"notebooks" ][ 0 ].rank == None
assert result[ u"notebooks" ][ 1 ].object_id
assert result[ u"notebooks" ][ 1 ].name == u"trash"
- assert result[ u"notebooks" ][ 1 ].read_write == True
+ assert result[ u"notebooks" ][ 1 ].read_write == Notebook.READ_WRITE
assert result[ u"notebooks" ][ 1 ].owner == True
assert result[ u"notebooks" ][ 1 ].rank == None
assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 0 ].object_id
assert result[ u"notebooks" ][ 2 ].name == self.notebooks[ 0 ].name
- assert result[ u"notebooks" ][ 2 ].read_write == True
+ assert result[ u"notebooks" ][ 2 ].read_write == Notebook.READ_WRITE
assert result[ u"notebooks" ][ 2 ].owner == True
assert result[ u"notebooks" ][ 2 ].rank == 0
assert result[ u"notebooks" ][ 3 ].object_id == self.notebooks[ 1 ].object_id
assert result[ u"notebooks" ][ 3 ].name == self.notebooks[ 1 ].name
- assert result[ u"notebooks" ][ 3 ].read_write == True
+ assert result[ u"notebooks" ][ 3 ].read_write == Notebook.READ_WRITE
assert result[ u"notebooks" ][ 3 ].owner == True
assert result[ u"notebooks" ][ 3 ].rank == 1
assert result[ u"notebooks" ][ 4 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 4 ].name == self.anon_notebook.name
- assert result[ u"notebooks" ][ 4 ].read_write == False
+ assert result[ u"notebooks" ][ 4 ].read_write == Notebook.READ_ONLY
assert result[ u"notebooks" ][ 4 ].owner == False
assert result[ u"notebooks" ][ 4 ].rank == None
assert result[ u"login_url" ] is None
@@ -783,7 +785,7 @@ class Test_users( Test_controller ):
assert len( result[ u"notebooks" ] ) == 1
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
- assert result[ u"notebooks" ][ 0 ].read_write == False
+ assert result[ u"notebooks" ][ 0 ].read_write == Notebook.READ_ONLY
assert result[ u"notebooks" ][ 0 ].owner == False
assert result[ u"notebooks" ][ 0 ].rank == None
@@ -831,8 +833,8 @@ class Test_users( Test_controller ):
invite_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
assert invite_notebook_id == self.notebooks[ 0 ].object_id
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
def test_login_with_after_login( self ):
after_login = u"/foo/bar"
@@ -895,45 +897,215 @@ class Test_users( Test_controller ):
assert user.group_storage_bytes == 0
assert user.revision > previous_revision
- def test_check_access( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id )
+ def test_load_notebook( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id )
- assert access is True
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
- def test_check_access_read_write( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True )
+ def test_load_notebook_unknown_notebook( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, u"unknownid" )
- assert access is True
+ assert notebook is None
- def test_check_access_owner( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, owner = True )
+ def test_load_notebook_unknown_user( self ):
+ notebook = cherrypy.root.users.load_notebook( u"unknownuser", self.notebooks[ 0 ].object_id )
- assert access is True
+ assert notebook is None
- def test_check_access_full( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True, owner = True )
+ def test_load_notebook_read_write( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True )
- assert access is True
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
- def test_check_access_anon( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id )
+ def test_load_notebook_owner( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id, owner = True )
- assert access is True
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
- def test_check_access_anon_read_write( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, read_write = True )
+ def test_load_notebook_full( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id, read_write = True, owner = True )
- assert access is False
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
- def test_check_access_anon_owner( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, owner = True )
+ def test_load_notebook_with_note_id( self ):
+ note = Note.create(
+ self.database.next_id( Note ), u"hi
",
+ notebook_id = self.notebooks[ 0 ].object_id,
+ user_id = self.user.object_id,
+ )
+ self.database.save( note )
- assert access is False
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
+ ) )
- def test_check_access_anon_full( self ):
- access = cherrypy.root.users.check_access( self.user.object_id, self.anon_notebook.object_id, read_write = True, owner = True )
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = note.object_id )
- assert access is False
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_with_note_id_by_another_user( self ):
+ note = Note.create(
+ self.database.next_id( Note ), u"hi from another user
",
+ notebook_id = self.notebooks[ 0 ].object_id,
+ user_id = self.user2.object_id,
+ )
+ self.database.save( note )
+
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = note.object_id )
+
+ assert notebook is None
+
+ def test_load_notebook_with_unknown_note_id( self ):
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = u"unknownid" )
+
+ assert notebook is None
+
+ def test_load_notebook_with_note_id_in_another_notebook( self ):
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_WRITE_FOR_OWN_NOTES, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = self.startup_note.object_id )
+
+ assert notebook is None
+
+ def test_load_notebook_read_write_with_note_id( self ):
+ note = Note.create(
+ self.database.next_id( Note ), u"hi
",
+ notebook_id = self.notebooks[ 0 ].object_id,
+ user_id = self.user.object_id,
+ )
+ self.database.save( note )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = note.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_write_with_note_id_by_another_user( self ):
+ note = Note.create(
+ self.database.next_id( Note ), u"hi from another user
",
+ notebook_id = self.notebooks[ 0 ].object_id,
+ user_id = self.user2.object_id,
+ )
+ self.database.save( note )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = note.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_write_with_unknown_note_id( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = u"unknownid" )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_write_with_note_id_in_another_notebook( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = self.startup_note.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_only_with_note_id( self ):
+ note = Note.create(
+ self.database.next_id( Note ), u"hi
",
+ notebook_id = self.notebooks[ 0 ].object_id,
+ user_id = self.user.object_id,
+ )
+ self.database.save( note )
+
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = note.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_only_with_note_id_by_another_user( self ):
+ note = Note.create(
+ self.database.next_id( Note ), u"hi from another user
",
+ notebook_id = self.notebooks[ 0 ].object_id,
+ user_id = self.user2.object_id,
+ )
+ self.database.save( note )
+
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = note.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_only_with_unknown_note_id( self ):
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = u"unknownid" )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_read_only_with_note_id_in_another_notebook( self ):
+ self.database.execute( self.user.sql_update_access(
+ self.notebooks[ 0 ].object_id, read_write = Notebook.READ_ONLY, owner = False,
+ ) )
+
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.notebooks[ 0 ].object_id,
+ note_id = self.startup_note.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.notebooks[ 0 ].object_id
+
+ def test_load_notebook_anon( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id )
+
+ assert notebook
+ assert notebook.object_id == self.anon_notebook.object_id
+
+ def test_load_notebook_anon_read_write( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id, read_write = True )
+
+ assert notebook is None
+
+ def test_load_notebook_anon_owner( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id, owner = True )
+
+ assert notebook is None
+
+ def test_load_notebook_anon_full( self ):
+ notebook = cherrypy.root.users.load_notebook( self.user.object_id, self.anon_notebook.object_id, read_write = True, owner = True )
+
+ assert notebook is None
def test_check_group( self ):
membership = cherrypy.root.users.check_group( self.user.object_id, self.group.object_id )
@@ -1097,7 +1269,7 @@ class Test_users( Test_controller ):
assert len( result[ u"notebooks" ] ) == 1
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 0 ].name == self.anon_notebook.name
- assert result[ u"notebooks" ][ 0 ].read_write == False
+ assert result[ u"notebooks" ][ 0 ].read_write == Notebook.READ_ONLY
assert result[ u"notebooks" ][ 0 ].owner == False
assert result[ u"notebooks" ][ 0 ].rank == None
@@ -2151,8 +2323,8 @@ class Test_users( Test_controller ):
invite_id = invite_id,
), session_id = self.session_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
self.login()
result = self.http_post( "/users/revoke_invite", dict(
@@ -2163,8 +2335,8 @@ class Test_users( Test_controller ):
assert result[ u"message" ]
assert len( result[ u"invites" ] ) == 0
- assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
def test_revoke_invite_redeemed_self( self ):
self.login()
@@ -2191,8 +2363,8 @@ class Test_users( Test_controller ):
invite_id = invite_id,
), session_id = self.session_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
# as user2, revoke that user's own invite
result = self.http_post( "/users/revoke_invite", dict(
@@ -2204,8 +2376,8 @@ class Test_users( Test_controller ):
assert len( result[ u"invites" ] ) == 0
# the user should no longer have any access
- assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert not cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert not cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
def test_revoke_invite_without_login( self ):
# 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 ):
), session_id = self.session_id )
# assert that access has been granted
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
# assert that the user is redirected to the notebook that the invite is for
assert result[ u"redirect"].startswith( u"/notebooks/" )
@@ -2385,8 +2557,8 @@ class Test_users( Test_controller ):
), session_id = self.session_id )
# assert that access is still granted
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].object_id )
- assert cherrypy.root.users.check_access( self.user2.object_id, self.notebooks[ 0 ].trash_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].object_id )
+ assert cherrypy.root.users.load_notebook( self.user2.object_id, self.notebooks[ 0 ].trash_id )
# assert that the user is redirected to the notebook that the invite is for
assert result[ u"redirect"].startswith( u"/notebooks/" )
@@ -4104,7 +4276,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4145,7 +4317,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4184,7 +4356,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4224,7 +4396,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4262,7 +4434,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4307,7 +4479,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4436,7 +4608,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4484,7 +4656,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4532,7 +4704,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4578,7 +4750,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4627,7 +4799,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
@@ -4675,7 +4847,7 @@ class Test_users( Test_controller ):
notebook = [ notebook for notebook in result[ u"notebooks" ] if notebook.object_id == self.notebooks[ 0 ].object_id ][ 0 ]
assert notebook.object_id == self.notebooks[ 0 ].object_id
assert notebook.name == self.notebooks[ 0 ].name
- assert notebook.read_write == True
+ assert notebook.read_write == Notebook.READ_WRITE
assert notebook.owner == True
assert notebook.rank == 0
diff --git a/luminotes.py b/luminotes.py
index e72d52c..d7e7022 100755
--- a/luminotes.py
+++ b/luminotes.py
@@ -9,8 +9,10 @@ import urllib2 as urllib
import cherrypy
import webbrowser
from controller.Database import Database
+from controller.Schema_upgrader import Schema_upgrader
from controller.Root import Root
from config import Common
+from config.Version import VERSION
INITIAL_SOCKET_TIMEOUT_SECONDS = 1
@@ -103,6 +105,10 @@ def main( args ):
ssl_mode = cherrypy.config.configMap[ u"global" ].get( u"luminotes.db_ssl_mode" ),
)
+ # if necessary, upgrade the database schema to match this current version of the code
+ schema_upgrader = Schema_upgrader( database )
+ schema_upgrader.upgrade_schema( to_version = VERSION )
+
cherrypy.lowercase_api = True
root = Root( database, cherrypy.config.configMap )
cherrypy.root = root
@@ -114,6 +120,7 @@ def callback( log_access_file, log_file, server_url, port_filename, socket_port,
# record our listening socket port
if port_filename:
port_file = file( port_filename, "w" )
+ os.chmod( port_filename, stat.S_IRUSR | stat.S_IWUSR )
port_file.write( "%s" % socket_port )
port_file.close()
diff --git a/model/Notebook.py b/model/Notebook.py
index f74b344..da290fe 100644
--- a/model/Notebook.py
+++ b/model/Notebook.py
@@ -12,8 +12,12 @@ class Notebook( Persistent ):
WHITESPACE_PATTERN = re.compile( r"\s+" )
SEARCH_OPERATORS = re.compile( r"[&|!()'\\:]" )
+ READ_ONLY = 0 # user can only view the notes within this notebook
+ READ_WRITE = 1 # user can view and edit the notes within this notebook
+ READ_WRITE_FOR_OWN_NOTES = 2 # user can only edit their own notes, not notes created by others
+
def __init__( self, object_id, revision = None, name = None, trash_id = None, deleted = False,
- user_id = None, read_write = True, owner = True, rank = None ):
+ user_id = None, read_write = None, owner = True, rank = None, own_notes_only = False ):
"""
Create a new notebook with the given id and name.
@@ -30,11 +34,14 @@ class Notebook( Persistent ):
@type user_id: unicode or NoneType
@param user_id: id of the user who most recently updated this notebook object (optional)
@type read_write: bool or NoneType
- @param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
+ @param read_write: whether this view of the notebook is currently read-write. one of:
+ READ_ONLY, READ_WRITE, READ_WRITE_FOR_OWN_NOTES (optional, defaults to READ_WRITE)
@type owner: bool or NoneType
@param owner: whether this view of the notebook currently has owner-level access (optional, defaults to True)
@type rank: float or NoneType
@param rank: indicates numeric ordering of this note in relation to other notebooks
+ @type own_notes_only: bool or NoneType
+ @param own_notes_only: True makes read_write be READ_WRITE_FOR_OWN_NOTES (optional, defaults to False)
@rtype: Notebook
@return: newly constructed notebook
"""
@@ -43,12 +50,22 @@ class Notebook( Persistent ):
self.__trash_id = trash_id
self.__deleted = deleted
self.__user_id = user_id
+
+ read_write = {
+ None: Notebook.READ_WRITE,
+ True: Notebook.READ_WRITE,
+ False: Notebook.READ_ONLY,
+ }.get( read_write, read_write )
+
+ if own_notes_only is True and read_write != Notebook.READ_ONLY:
+ read_write = Notebook.READ_WRITE_FOR_OWN_NOTES
+
self.__read_write = read_write
self.__owner = owner
self.__rank = rank
@staticmethod
- def create( object_id, name = None, trash_id = None, deleted = False, user_id = None, read_write = True, owner = True, rank = None ):
+ 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 ):
"""
Convenience constructor for creating a new notebook.
@@ -63,15 +80,18 @@ class Notebook( Persistent ):
@type user_id: unicode or NoneType
@param user_id: id of the user who most recently updated this notebook object (optional)
@type read_write: bool or NoneType
- @param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
+ @param read_write: whether this view of the notebook is currently read-write. one of:
+ READ_ONLY, READ_WRITE, READ_WRITE_FOR_OWN_NOTES (optional, defaults to READ_WRITE)
@type owner: bool or NoneType
@param owner: whether this view of the notebook currently has owner-level access (optional, defaults to True)
@type rank: float or NoneType
@param rank: indicates numeric ordering of this note in relation to other notebooks
+ @type own_notes_only: bool or NoneType
+ @param own_notes_only: True makes read_write be READ_WRITE_FOR_OWN_NOTES (optional, defaults to False)
@rtype: Notebook
@return: newly constructed notebook
"""
- return Notebook( object_id, name = name, trash_id = trash_id, user_id = user_id, read_write = read_write, owner = owner, rank = rank )
+ 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 )
@staticmethod
def sql_load( object_id, revision = None ):
@@ -264,6 +284,42 @@ class Notebook( Persistent ):
"select count( id ) from note_current where notebook_id = %s;" % \
( quote( self.object_id ) )
+ def sql_load_tag_by_name( self, user_id, tag_name ):
+ """
+ Return a SQL string to load a tag associated with this notebook by the given user.
+ """
+ return \
+ """
+ select
+ tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description, tag_notebook.value
+ from
+ tag_notebook, tag
+ where
+ tag_notebook.notebook_id = %s and
+ tag_notebook.user_id = %s and
+ tag_notebook.tag_id = tag.id and
+ tag.name = %s
+ order by tag.name;
+ """ % ( quote( self.object_id ), quote( user_id ), quote( tag_name ) )
+
+ def sql_load_tags( self, user_id ):
+ """
+ Return a SQL string to load a list of all the tags associated with this notebook by the given
+ user.
+ """
+ return \
+ """
+ select
+ tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description, tag_notebook.value
+ from
+ tag_notebook, tag
+ where
+ tag_notebook.notebook_id = %s and
+ tag_notebook.user_id = %s and
+ tag_notebook.tag_id = tag.id
+ order by tag.name;
+ """ % ( quote( self.object_id ), quote( user_id ) )
+
def to_dict( self ):
d = Persistent.to_dict( self )
@@ -285,6 +341,12 @@ class Notebook( Persistent ):
def __set_read_write( self, read_write ):
# The read_write member isn't actually saved to the database, so setting it doesn't need to
# call update_revision().
+ read_write = {
+ None: Notebook.READ_WRITE,
+ True: Notebook.READ_WRITE,
+ False: Notebook.READ_ONLY,
+ }.get( read_write, read_write )
+
self.__read_write = read_write
def __set_owner( self, owner ):
diff --git a/model/Tag.py b/model/Tag.py
new file mode 100644
index 0000000..8ba77dd
--- /dev/null
+++ b/model/Tag.py
@@ -0,0 +1,135 @@
+from Persistent import Persistent, quote
+
+
+class Tag( Persistent ):
+ """
+ A tag for a note or a notebook.
+ """
+ def __init__( self, object_id, revision = None, notebook_id = None, user_id = None, name = None, description = None, value = None ):
+ """
+ Create a Tag with the given id.
+
+ @type object_id: unicode
+ @param object_id: id of the Tag
+ @type revision: datetime or NoneType
+ @param revision: revision timestamp of the object (optional, defaults to now)
+ @type notebook_id: unicode or NoneType
+ @param notebook_id: id of the notebook whose namespace this tag is in, if any
+ @type user_id: unicode or NoneType
+ @param user_id: id of the user who most recently updated this tag, if any
+ @type name: unicode or NoneType
+ @param name: name of the tag (optional)
+ @type description: unicode or NoneType
+ @param description: brief description of the tag (optional)
+ @type value: unicode or NoneType
+ @param value: per-note or per-notebook value of the tag (optional)
+ @rtype: Tag
+ @return: newly constructed Tag
+ """
+ Persistent.__init__( self, object_id, revision )
+ self.__notebook_id = notebook_id
+ self.__user_id = user_id
+ self.__name = name
+ self.__description = description
+ self.__value = value
+
+ @staticmethod
+ def create( object_id, notebook_id = None, user_id = None, name = None, description = None, value = None ):
+ """
+ Convenience constructor for creating a new Tag.
+
+ @type object_id: unicode
+ @param object_id: id of the Tag
+ @type notebook_id: unicode or NoneType
+ @param notebook_id: id of the notebook whose namespace this tag is in, if any
+ @type user_id: unicode or NoneType
+ @param user_id: id of the user who most recently updated this tag, if any
+ @type name: unicode or NoneType
+ @param name: name of the tag (optional)
+ @type description: unicode or NoneType
+ @param description: brief description of the tag (optional)
+ @type value: unicode or NoneType
+ @param value: per-note or per-notebook value of the tag (optional)
+ @rtype: Tag
+ @return: newly constructed Tag
+ """
+ return Tag( object_id, notebook_id = notebook_id, user_id = user_id, name = name, description = description, value = value )
+
+ @staticmethod
+ def sql_load( object_id, revision = None ):
+ # Tags don't store old revisions
+ if revision:
+ raise NotImplementedError()
+
+ return \
+ """
+ select
+ tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description
+ from
+ tag
+ where
+ tag.id = %s;
+ """ % quote( object_id )
+
+ @staticmethod
+ def sql_load_by_name( name, notebook_id = None, user_id = None ):
+ if notebook_id:
+ notebook_id_clause = " and tag.notebook_id = %s" % quote( notebook_id )
+ else:
+ notebook_id_clause = ""
+
+ if user_id:
+ user_id_clause = " and tag.user_id = %s" % quote( user_id )
+ else:
+ user_id_clause = ""
+
+ return \
+ """
+ select
+ tag.id, tag.revision, tag.notebook_id, tag.user_id, tag.name, tag.description
+ from
+ tag
+ where
+ tag.name = %s%s%s;
+ """ % ( quote( name ), notebook_id_clause, user_id_clause )
+
+ @staticmethod
+ def sql_id_exists( object_id, revision = None ):
+ if revision:
+ raise NotImplementedError()
+
+ return "select id from tag where id = %s;" % quote( object_id )
+
+ def sql_exists( self ):
+ return Tag.sql_id_exists( self.object_id )
+
+ def sql_create( self ):
+ return "insert into tag ( id, revision, notebook_id, user_id, name, description ) values ( %s, %s, %s, %s, %s, %s );" % \
+ ( quote( self.object_id ), quote( self.revision ), quote( self.__notebook_id ),
+ quote( self.__user_id ), quote( self.__name ), quote( self.__description ) )
+
+ def sql_update( self ):
+ return "update tag set revision = %s, notebook_id = %s, user_id = %s, name = %s, description = %s where id = %s;" % \
+ ( quote( self.revision ), quote( self.__notebook_id ), quote( self.__user_id ),
+ quote( self.__name ), quote( self.__description ), quote( self.object_id ) )
+
+ def sql_delete( self ):
+ return "delete from tag where id = %s;" % quote( self.object_id )
+
+ def to_dict( self ):
+ d = Persistent.to_dict( self )
+ d.update( dict(
+ notebook_id = self.__notebook_id,
+ user_id = self.__user_id,
+ name = self.__name,
+ description = self.__description,
+ value = self.__value,
+ ) )
+
+ return d
+
+ notebook_id = property( lambda self: self.__notebook_id )
+ user_id = property( lambda self: self.__user_id )
+ name = property( lambda self: self.__name )
+ description = property( lambda self: self.__description )
+ value = property( lambda self: self.__value )
diff --git a/model/User.py b/model/User.py
index 80a19f3..97d2c59 100644
--- a/model/User.py
+++ b/model/User.py
@@ -2,6 +2,7 @@ import sha
import random
from copy import copy
from Persistent import Persistent, quote
+from Notebook import Notebook
class User( Persistent ):
@@ -132,7 +133,8 @@ class User( Persistent ):
def sql_load_by_email_address( email_address ):
return "select * from luminotes_user_current where email_address = %s;" % quote( email_address )
- def sql_load_notebooks( self, parents_only = False, undeleted_only = False, read_write = False ):
+ def sql_load_notebooks( self, parents_only = False, undeleted_only = False, read_write = False,
+ tag_name = None, tag_value = None, notebook_id = None ):
"""
Return a SQL string to load a list of the notebooks to which this user has access.
"""
@@ -151,28 +153,55 @@ class User( Persistent ):
else:
read_write_clause = ""
+ if tag_name:
+ tag_tables = ", tag_notebook, tag"
+ tag_clause = \
+ """
+ and tag_notebook.tag_id = tag.id and tag_notebook.user_id = %s and
+ tag_notebook.notebook_id = notebook_current.id and tag.name = %s
+ """ % ( quote( self.object_id ), quote( tag_name ) )
+
+ if tag_value:
+ tag_clause += " and tag_notebook.value = %s" % quote( tag_value )
+ else:
+ tag_tables = ""
+ tag_clause = ""
+
+ # useful for loading just a single notebook that the user has access to
+ if notebook_id:
+ notebook_id_clause = " and notebook_current.id = %s" % quote( notebook_id )
+ else:
+ notebook_id_clause = ""
+
return \
"""
select
- notebook_current.*, user_notebook.read_write, user_notebook.owner, user_notebook.rank
+ notebook_current.*, user_notebook.read_write, user_notebook.owner, user_notebook.rank, user_notebook.own_notes_only
from
- user_notebook, notebook_current
+ user_notebook, notebook_current%s
where
- user_notebook.user_id = %s%s%s%s and
+ user_notebook.user_id = %s%s%s%s%s%s and
user_notebook.notebook_id = notebook_current.id
order by user_notebook.rank;
- """ % ( quote( self.object_id ), parents_only_clause, undeleted_only_clause, read_write_clause )
+ """ % ( tag_tables, quote( self.object_id ), parents_only_clause, undeleted_only_clause,
+ read_write_clause, tag_clause, notebook_id_clause )
- def sql_save_notebook( self, notebook_id, read_write = True, owner = True, rank = None ):
+ def sql_save_notebook( self, notebook_id, read_write = True, owner = True, rank = None, own_notes_only = False ):
"""
Return a SQL string to save the id of a notebook to which this user has access.
"""
if rank is None: rank = quote( None )
return \
- "insert into user_notebook ( user_id, notebook_id, read_write, owner, rank ) values " + \
- "( %s, %s, %s, %s, %s );" % ( quote( self.object_id ), quote( notebook_id ), quote( read_write and 't' or 'f' ),
- quote( owner and 't' or 'f' ), rank )
+ "insert into user_notebook ( user_id, notebook_id, read_write, owner, rank, own_notes_only ) values " + \
+ "( %s, %s, %s, %s, %s, %s );" % (
+ quote( self.object_id ),
+ quote( notebook_id ),
+ quote( read_write and 't' or 'f' ),
+ quote( owner and 't' or 'f' ),
+ rank,
+ quote( own_notes_only and 't' or 'f' ),
+ )
def sql_remove_notebook( self, notebook_id ):
"""
@@ -202,14 +231,26 @@ class User( Persistent ):
"select user_id from user_notebook where user_id = %s and notebook_id = %s;" % \
( quote( self.object_id ), quote( notebook_id ) )
- def sql_update_access( self, notebook_id, read_write = False, owner = False ):
+ def sql_update_access( self, notebook_id, read_write = Notebook.READ_ONLY, owner = False ):
"""
Return a SQL string to update the user's notebook access to the given read_write and owner level.
"""
return \
- "update user_notebook set read_write = %s, owner = %s where user_id = %s and notebook_id = %s;" % \
- ( quote( read_write and 't' or 'f' ), quote( owner and 't' or 'f' ), quote( self.object_id ),
- quote( notebook_id ) )
+ "update user_notebook set read_write = %s, owner = %s, own_notes_only = %s where user_id = %s and notebook_id = %s;" % (
+ quote( ( read_write != Notebook.READ_ONLY ) and 't' or 'f' ),
+ quote( owner and 't' or 'f' ),
+ quote( ( read_write == Notebook.READ_WRITE_FOR_OWN_NOTES ) and 't' or 'f' ),
+ quote( self.object_id ),
+ quote( notebook_id ),
+ )
+
+ def sql_save_notebook_tag( self, notebook_id, tag_id, value = None ):
+ """
+ Return a SQL string to associate a tag with a notebook of this user.
+ """
+ return \
+ "insert into tag_notebook ( notebook_id, tag_id, value, user_id ) values " + \
+ "( %s, %s, %s, %s );" % ( quote( notebook_id ), quote( tag_id ), quote( value ), quote( self.object_id ) )
def sql_update_notebook_rank( self, notebook_id, rank ):
"""
diff --git a/model/delta/1.5.5.sql b/model/delta/1.5.5.sql
new file mode 100644
index 0000000..b39b9bb
--- /dev/null
+++ b/model/delta/1.5.5.sql
@@ -0,0 +1,30 @@
+create table tag (
+ id text,
+ revision timestamp with time zone,
+ notebook_id text,
+ user_id text,
+ name text,
+ description text
+);
+ALTER TABLE ONLY tag ADD CONSTRAINT tag_pkey PRIMARY KEY (id);
+CREATE INDEX tag_notebook_id_index ON tag USING btree (notebook_id);
+CREATE INDEX tag_user_id_index ON tag USING btree (user_id);
+
+create table tag_notebook (
+ notebook_id text,
+ tag_id text,
+ value text,
+ user_id text
+);
+ALTER TABLE ONLY tag_notebook ADD CONSTRAINT tag_notebook_pkey PRIMARY KEY (user_id, notebook_id, tag_id);
+
+create table tag_note (
+ note_id text,
+ tag_id text,
+ value text
+);
+ALTER TABLE ONLY tag_note ADD CONSTRAINT tag_note_pkey PRIMARY KEY (note_id, tag_id);
+
+ALTER TABLE user_notebook ADD COLUMN own_notes_only boolean DEFAULT false;
+
+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';
diff --git a/model/delta/1.5.5.sqlite b/model/delta/1.5.5.sqlite
new file mode 100644
index 0000000..22091af
--- /dev/null
+++ b/model/delta/1.5.5.sqlite
@@ -0,0 +1,37 @@
+create table tag (
+ id text,
+ revision timestamp with time zone,
+ notebook_id text,
+ user_id text,
+ name text,
+ description text
+);
+CREATE INDEX tag_pkey ON tag (id);
+CREATE INDEX tag_notebook_id_index ON tag (notebook_id);
+CREATE INDEX tag_user_id_index ON tag (user_id);
+
+create table tag_notebook (
+ notebook_id text,
+ tag_id text,
+ value text,
+ user_id text
+);
+CREATE INDEX tag_notebook_pkey ON tag_notebook (user_id, notebook_id, tag_id);
+
+create table tag_note (
+ note_id text,
+ tag_id text,
+ value text
+);
+CREATE INDEX tag_note_pkey ON tag_note (note_id, tag_id);
+
+CREATE INDEX file_pkey ON file (id);
+CREATE INDEX invite_pkey ON invite (id);
+CREATE INDEX luminotes_user_pkey ON luminotes_user (id, revision);
+CREATE INDEX note_pkey ON note (id, revision);
+CREATE INDEX notebook_pkey ON notebook (id, revision);
+CREATE INDEX password_reset_pkey ON password_reset (id);
+CREATE INDEX download_access_pkey ON download_access (id);
+CREATE INDEX user_notebook_pkey ON user_notebook (user_id, notebook_id);
+
+ALTER TABLE user_notebook ADD COLUMN own_notes_only boolean DEFAULT false;
diff --git a/model/drop.sql b/model/drop.sql
index c392d37..f6cff28 100644
--- a/model/drop.sql
+++ b/model/drop.sql
@@ -12,4 +12,7 @@ DROP TABLE user_notebook;
DROP TABLE user_group;
DROP TABLE invite;
DROP TABLE file;
+DROP TABLE tag;
+DROP TABLE tag_notebook;
+DROP TABLE tag_note;
DROP FUNCTION drop_html_tags( text );
diff --git a/model/schema.sql b/model/schema.sql
index 716f760..1acbb05 100644
--- a/model/schema.sql
+++ b/model/schema.sql
@@ -1,5 +1,5 @@
--
--- PostgreSQL database dump
+-- PostgreSQL database schema
--
SET client_encoding = 'UTF8';
@@ -12,22 +12,10 @@ SET default_tablespace = '';
SET default_with_oids = false;
-
---
--- Name: drop_html_tags(text); Type: FUNCTION; Schema: public; Owner: luminotes
---
-
CREATE FUNCTION drop_html_tags(text) RETURNS text
AS $_$select regexp_replace( regexp_replace( $1, '?(div|p|br|ul|ol|li|h3)( [^>]*?)?/?>', ' ', 'gi' ), '<[^>]+?>', '', 'g' );$_$
LANGUAGE sql;
-
-
ALTER FUNCTION public.drop_html_tags(text) OWNER TO luminotes;
-
---
--- Name: file; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE file (
id text NOT NULL,
revision timestamp with time zone,
@@ -37,14 +25,30 @@ CREATE TABLE file (
size_bytes integer,
content_type text
);
-
-
ALTER TABLE public.file OWNER TO luminotes;
+CREATE TABLE tag (
+ id text NOT NULL,
+ revision timestamp with time zone,
+ notebook_id text,
+ user_id text,
+ name text,
+ description text
+);
+ALTER TABLE public.tag OWNER TO luminotes;
+CREATE TABLE tag_notebook (
+ notebook_id text,
+ tag_id text,
+ value text,
+ user_id text
+);
---
--- Name: invite; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
+ALTER TABLE public.tag_notebook OWNER TO luminotes;
+CREATE TABLE tag_note (
+ note_id text,
+ tag_id text,
+ value text
+);
+ALTER TABLE public.tag_note OWNER TO luminotes;
CREATE TABLE invite (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -55,37 +59,16 @@ CREATE TABLE invite (
"owner" boolean,
redeemed_user_id text
);
-
-
ALTER TABLE public.invite OWNER TO luminotes;
-
---
--- Name: luminotes_group; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE luminotes_group (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
name text
);
-
-
ALTER TABLE public.luminotes_group OWNER TO luminotes;
-
---
--- Name: luminotes_group_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW luminotes_group_current AS
SELECT luminotes_group.id, luminotes_group.revision, luminotes_group.name FROM luminotes_group WHERE (luminotes_group.revision IN (SELECT max(sub_group.revision) AS max FROM luminotes_group sub_group WHERE (sub_group.id = luminotes_group.id)));
-
-
ALTER TABLE public.luminotes_group_current OWNER TO luminotes;
-
---
--- Name: luminotes_user; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE luminotes_user (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -96,24 +79,10 @@ CREATE TABLE luminotes_user (
storage_bytes integer,
rate_plan integer
);
-
-
ALTER TABLE public.luminotes_user OWNER TO luminotes;
-
---
--- Name: luminotes_user_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW luminotes_user_current AS
SELECT luminotes_user.id, luminotes_user.revision, luminotes_user.username, luminotes_user.salt, luminotes_user.password_hash, luminotes_user.email_address, luminotes_user.storage_bytes, luminotes_user.rate_plan FROM luminotes_user WHERE (luminotes_user.revision IN (SELECT max(sub_user.revision) AS max FROM luminotes_user sub_user WHERE (sub_user.id = luminotes_user.id)));
-
-
ALTER TABLE public.luminotes_user_current OWNER TO luminotes;
-
---
--- Name: note; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE note (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -126,24 +95,10 @@ CREATE TABLE note (
search tsvector,
user_id text
);
-
-
ALTER TABLE public.note OWNER TO luminotes;
-
---
--- Name: note_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW note_current AS
SELECT note.id, note.revision, note.title, note.contents, note.notebook_id, note.startup, note.deleted_from_id, note.rank, note.search, note.user_id FROM note WHERE (note.revision IN (SELECT max(sub_note.revision) AS max FROM note sub_note WHERE (sub_note.id = note.id)));
-
-
ALTER TABLE public.note_current OWNER TO luminotes;
-
---
--- Name: notebook; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE notebook (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -152,239 +107,106 @@ CREATE TABLE notebook (
deleted boolean DEFAULT false,
user_id text
);
-
-
ALTER TABLE public.notebook OWNER TO luminotes;
-
---
--- Name: notebook_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW notebook_current AS
SELECT notebook.id, notebook.revision, notebook.name, notebook.trash_id, notebook.deleted, notebook.user_id FROM notebook WHERE (notebook.revision IN (SELECT max(sub_notebook.revision) AS max FROM notebook sub_notebook WHERE (sub_notebook.id = notebook.id)));
-
-
ALTER TABLE public.notebook_current OWNER TO luminotes;
-
---
--- Name: password_reset; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE password_reset (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
email_address text,
redeemed boolean
);
-
-
ALTER TABLE public.password_reset OWNER TO luminotes;
-
--- Name: download_access; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE download_access (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
item_number text,
transaction_id text
);
-
-
ALTER TABLE public.download_access OWNER TO luminotes;
-
---
---
--- Name: user_group; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE user_group (
user_id text NOT NULL,
group_id text NOT NULL,
"admin" boolean DEFAULT false
);
-
-
ALTER TABLE public.user_group OWNER TO luminotes;
-
---
--- Name: user_notebook; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE user_notebook (
user_id text NOT NULL,
notebook_id text NOT NULL,
read_write boolean DEFAULT false,
"owner" boolean DEFAULT false,
- rank numeric
+ rank numeric,
+ own_notes_only boolean DEFAULT false
);
-
-
ALTER TABLE public.user_notebook OWNER TO luminotes;
-
--- Name: file_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY file
ADD CONSTRAINT file_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY tag
+ ADD CONSTRAINT tag_pkey PRIMARY KEY (id);
---
--- Name: invite_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
+ALTER TABLE ONLY tag_notebook
+ ADD CONSTRAINT tag_notebook_pkey PRIMARY KEY (user_id, notebook_id, tag_id);
+
+ALTER TABLE ONLY tag_note
+ ADD CONSTRAINT tag_note_pkey PRIMARY KEY (note_id, tag_id);
ALTER TABLE ONLY invite
ADD CONSTRAINT invite_pkey PRIMARY KEY (id);
-
---
--- Name: luminotes_user_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY luminotes_user
ADD CONSTRAINT luminotes_user_pkey PRIMARY KEY (id, revision);
-
---
--- Name: note_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY note
ADD CONSTRAINT note_pkey PRIMARY KEY (id, revision);
-
---
--- Name: notebook_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY notebook
ADD CONSTRAINT notebook_pkey PRIMARY KEY (id, revision);
-
---
--- Name: password_reset_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY password_reset
ADD CONSTRAINT password_reset_pkey PRIMARY KEY (id);
-
---
--- Name: download_access_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY download_access
ADD CONSTRAINT download_access_pkey PRIMARY KEY (id);
-
---
--- Name: user_notebook_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
---
-
ALTER TABLE ONLY user_notebook
ADD CONSTRAINT user_notebook_pkey PRIMARY KEY (user_id, notebook_id);
-
---
--- Name: file_note_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX file_note_id_index ON file USING btree (note_id);
-
---
--- Name: file_notebook_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX file_notebook_id_index ON file USING btree (notebook_id);
+CREATE INDEX tag_notebook_id_index ON tag USING btree (notebook_id);
---
--- Name: luminotes_group_pkey; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
+CREATE INDEX tag_user_id_index ON tag USING btree (user_id);
CREATE INDEX luminotes_group_pkey ON luminotes_group USING btree (id, revision);
-
---
--- Name: luminotes_user_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX luminotes_user_email_address_index ON luminotes_user USING btree (email_address);
-
---
--- Name: luminotes_user_username_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX luminotes_user_username_index ON luminotes_user USING btree (username);
-
---
--- Name: note_notebook_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX note_notebook_id_index ON note USING btree (notebook_id);
-
---
--- Name: note_notebook_id_startup_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX note_notebook_id_startup_index ON note USING btree (notebook_id, startup);
-
---
--- Name: note_notebook_id_title_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX note_notebook_id_title_index ON note USING btree (notebook_id, md5(title));
-
---
--- Name: password_reset_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX password_reset_email_address_index ON password_reset USING btree (email_address);
-
---
--- Name: download_access_transaction_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX download_access_transaction_id_index ON download_access USING btree (transaction_id);
-
---
--- Name: search_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX search_index ON note USING gist (search);
-
---
--- Name: search_update; Type: TRIGGER; Schema: public; Owner: luminotes
---
-
CREATE TRIGGER search_update
BEFORE INSERT OR UPDATE ON note
FOR EACH ROW
EXECUTE PROCEDURE tsearch2('search', 'drop_html_tags', 'title', 'contents');
-
---
--- Name: public; Type: ACL; Schema: -; Owner: postgres
---
-
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;
-
-
---
--- PostgreSQL database dump complete
---
-
diff --git a/model/schema.sqlite b/model/schema.sqlite
index a8563eb..707f0cd 100644
--- a/model/schema.sqlite
+++ b/model/schema.sqlite
@@ -1,5 +1,5 @@
--
--- Name: file; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
+-- SQLite database schema
--
CREATE TABLE file (
@@ -12,10 +12,27 @@ CREATE TABLE file (
content_type text
);
+CREATE TABLE tag (
+ id text NOT NULL,
+ revision timestamp with time zone,
+ notebook_id text,
+ user_id text,
+ name text,
+ description text
+);
---
--- Name: invite; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
+CREATE TABLE tag_notebook (
+ notebook_id text,
+ tag_id text,
+ value text,
+ user_id text
+);
+
+CREATE TABLE tag_note (
+ note_id text,
+ tag_id text,
+ value text
+);
CREATE TABLE invite (
id text NOT NULL,
@@ -28,30 +45,15 @@ CREATE TABLE invite (
redeemed_user_id text
);
-
---
--- Name: luminotes_group; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE luminotes_group (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
name text
);
-
---
--- Name: luminotes_group_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW luminotes_group_current AS
SELECT id, revision, name FROM luminotes_group WHERE (luminotes_group.revision IN (SELECT max(sub_group.revision) AS max FROM luminotes_group sub_group WHERE (sub_group.id = luminotes_group.id)));
-
---
--- Name: luminotes_user; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE luminotes_user (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -63,19 +65,9 @@ CREATE TABLE luminotes_user (
rate_plan integer
);
-
---
--- Name: luminotes_user_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW luminotes_user_current AS
SELECT id, revision, username, salt, password_hash, email_address, storage_bytes, rate_plan FROM luminotes_user WHERE (luminotes_user.revision IN (SELECT max(sub_user.revision) AS max FROM luminotes_user sub_user WHERE (sub_user.id = luminotes_user.id)));
-
---
--- Name: note; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE note (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -89,19 +81,9 @@ CREATE TABLE note (
user_id text
);
-
---
--- Name: note_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW note_current AS
SELECT id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, search, user_id FROM note WHERE (note.revision IN (SELECT max(sub_note.revision) AS max FROM note sub_note WHERE (sub_note.id = note.id)));
-
---
--- Name: notebook; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE notebook (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -111,19 +93,9 @@ CREATE TABLE notebook (
user_id text
);
-
---
--- Name: notebook_current; Type: VIEW; Schema: public; Owner: luminotes
---
-
CREATE VIEW notebook_current AS
SELECT id, revision, name, trash_id, deleted, user_id FROM notebook WHERE (notebook.revision IN (SELECT max(sub_notebook.revision) AS max FROM notebook sub_notebook WHERE (sub_notebook.id = notebook.id)));
-
---
--- Name: password_reset; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE password_reset (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -131,10 +103,6 @@ CREATE TABLE password_reset (
redeemed boolean
);
-
--- Name: download_access; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE download_access (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@@ -142,118 +110,69 @@ CREATE TABLE download_access (
transaction_id text
);
-
---
--- Name: user_group; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE user_group (
user_id text NOT NULL,
group_id text NOT NULL,
"admin" boolean DEFAULT false
);
-
---
--- Name: user_notebook; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE TABLE user_notebook (
user_id text NOT NULL,
notebook_id text NOT NULL,
read_write boolean DEFAULT false,
"owner" boolean DEFAULT false,
- rank numeric
+ rank numeric,
+ own_notes_only boolean DEFAULT false
);
+CREATE INDEX file_pkey ON file (id);
+CREATE INDEX tag_pkey ON tag (id);
---
--- Name: file_note_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
+CREATE INDEX tag_notebook_pkey ON tag_notebook (user_id, notebook_id, tag_id);
+
+CREATE INDEX tag_note_pkey ON tag_note (note_id, tag_id);
+
+CREATE INDEX invite_pkey ON invite (id);
+
+CREATE INDEX luminotes_user_pkey ON luminotes_user (id, revision);
+
+CREATE INDEX note_pkey ON note (id, revision);
+
+CREATE INDEX notebook_pkey ON notebook (id, revision);
+
+CREATE INDEX password_reset_pkey ON password_reset (id);
+
+CREATE INDEX download_access_pkey ON download_access (id);
+
+CREATE INDEX user_notebook_pkey ON user_notebook (user_id, notebook_id);
CREATE INDEX file_note_id_index ON file (note_id);
-
---
--- Name: file_notebook_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX file_notebook_id_index ON file (notebook_id);
+CREATE INDEX tag_notebook_id_index ON tag (notebook_id);
---
--- Name: luminotes_group_pkey; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
+CREATE INDEX tag_user_id_index ON tag (user_id);
CREATE INDEX luminotes_group_pkey ON luminotes_group (id, revision);
-
---
--- Name: luminotes_user_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX luminotes_user_email_address_index ON luminotes_user (email_address);
-
---
--- Name: luminotes_user_username_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX luminotes_user_username_index ON luminotes_user (username);
-
---
--- Name: note_notebook_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX note_notebook_id_index ON note (notebook_id);
-
---
--- Name: note_notebook_id_startup_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX note_notebook_id_startup_index ON note (notebook_id, startup);
-
---
--- Name: note_notebook_id_title_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX note_notebook_id_title_index ON note (notebook_id, title);
-
---
--- Name: password_reset_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX password_reset_id_index ON password_reset (id);
-
---
--- Name: password_reset_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX password_reset_email_address_index ON password_reset (email_address);
-
--- Name: download_access_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX download_access_id_index ON password_reset (id);
-
--- Name: download_access_transaction_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX download_access_transaction_id_index ON download_access (transaction_id);
-
---
--- Name: search_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
---
-
CREATE INDEX search_index ON note (search);
-
--- vim: ft=sql
diff --git a/model/test/Test_notebook.py b/model/test/Test_notebook.py
index c506605..3d7ee0b 100644
--- a/model/test/Test_notebook.py
+++ b/model/test/Test_notebook.py
@@ -12,11 +12,11 @@ class Test_notebook( object ):
self.trash_name = u"trash"
self.user_id = u"me"
self.delta = timedelta( seconds = 1 )
- self.read_write = True
+ self.read_write = Notebook.READ_WRITE
self.owner = False
self.rank = 17.5
- self.trash = Notebook.create( self.trash_id, self.trash_name, read_write = False, deleted = False, user_id = self.user_id )
+ self.trash = Notebook.create( self.trash_id, self.trash_name, read_write = Notebook.READ_ONLY, deleted = False, user_id = self.user_id )
self.notebook = Notebook.create( self.object_id, self.name, trash_id = self.trash.object_id, deleted = False, user_id = self.user_id, read_write = self.read_write, owner = self.owner, rank = self.rank )
self.note = Note.create( "19", u"title
blah" )
@@ -24,7 +24,6 @@ class Test_notebook( object ):
assert self.notebook.object_id == self.object_id
assert datetime.now( tz = utc ) - self.notebook.revision < self.delta
assert self.notebook.name == self.name
- assert self.notebook.read_write == True
assert self.notebook.trash_id == self.trash_id
assert self.notebook.deleted == False
assert self.notebook.user_id == self.user_id
@@ -35,14 +34,104 @@ class Test_notebook( object ):
assert self.trash.object_id == self.trash_id
assert datetime.now( tz = utc ) - self.trash.revision < self.delta
assert self.trash.name == self.trash_name
- assert self.trash.read_write == False
assert self.trash.trash_id == None
assert self.trash.deleted == False
assert self.trash.user_id == self.user_id
- assert self.trash.read_write == False
+ assert self.trash.read_write == Notebook.READ_ONLY
assert self.trash.owner == True
assert self.trash.rank == None
+ def test_create_read_write_true( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = True, owner = self.owner, rank = self.rank )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_WRITE
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
+ def test_create_read_write_false( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = False, owner = self.owner, rank = self.rank )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_ONLY
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
+ def test_create_read_write_none( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = None, owner = self.owner, rank = self.rank )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_WRITE
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
+ def test_create_read_write_true_and_own_notes_only_true( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = True, owner = self.owner, rank = self.rank, own_notes_only = True )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
+ def test_create_read_write_false_and_own_notes_only_true( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = False, owner = self.owner, rank = self.rank, own_notes_only = True )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_ONLY
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
+ def test_create_read_write_false_and_own_notes_only_false( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = False, owner = self.owner, rank = self.rank, own_notes_only = False )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_ONLY
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
+ def test_create_read_write_true_and_own_notes_only_false( self ):
+ notebook = Notebook.create( self.object_id, self.name, trash_id = None, deleted = False, user_id = self.user_id, read_write = True, owner = self.owner, rank = self.rank, own_notes_only = False )
+
+ assert notebook.object_id == self.object_id
+ assert datetime.now( tz = utc ) - notebook.revision < self.delta
+ assert notebook.name == self.name
+ assert notebook.trash_id == None
+ assert notebook.deleted == False
+ assert notebook.user_id == self.user_id
+ assert notebook.read_write == Notebook.READ_WRITE
+ assert notebook.owner == self.owner
+ assert notebook.rank == self.rank
+
def test_set_name( self ):
new_name = u"my new notebook"
previous_revision = self.notebook.revision
@@ -52,10 +141,31 @@ class Test_notebook( object ):
assert self.notebook.revision > previous_revision
def test_set_read_write( self ):
+ original_revision = self.notebook.revision
+ self.notebook.read_write = Notebook.READ_WRITE_FOR_OWN_NOTES
+
+ assert self.notebook.read_write == Notebook.READ_WRITE_FOR_OWN_NOTES
+ assert self.notebook.revision == original_revision
+
+ def test_set_read_write_true( self ):
original_revision = self.notebook.revision
self.notebook.read_write = True
- assert self.notebook.read_write == True
+ assert self.notebook.read_write == Notebook.READ_WRITE
+ assert self.notebook.revision == original_revision
+
+ def test_set_read_write_false( self ):
+ original_revision = self.notebook.revision
+ self.notebook.read_write = False
+
+ assert self.notebook.read_write == Notebook.READ_ONLY
+ assert self.notebook.revision == original_revision
+
+ def test_set_read_write_none( self ):
+ original_revision = self.notebook.revision
+ self.notebook.read_write = None
+
+ assert self.notebook.read_write == Notebook.READ_WRITE
assert self.notebook.revision == original_revision
def test_set_deleted( self ):
@@ -84,7 +194,7 @@ class Test_notebook( object ):
assert d.get( "name" ) == self.name
assert d.get( "trash_id" ) == self.trash.object_id
- assert d.get( "read_write" ) == True
+ assert d.get( "read_write" ) == self.read_write
assert d.get( "deleted" ) == self.notebook.deleted
assert d.get( "user_id" ) == self.notebook.user_id
assert d.get( "object_id" ) == self.notebook.object_id
diff --git a/model/test/Test_tag.py b/model/test/Test_tag.py
new file mode 100644
index 0000000..a0aaab2
--- /dev/null
+++ b/model/test/Test_tag.py
@@ -0,0 +1,36 @@
+from pytz import utc
+from datetime import datetime, timedelta
+from model.Tag import Tag
+
+
+class Test_tag( object ):
+ def setUp( self ):
+ self.object_id = u"17"
+ self.notebook_id = u"19"
+ self.user_id = u"20"
+ self.name = u"mytag"
+ self.description = u"this is my tag"
+ self.value = u"a value"
+ self.delta = timedelta( seconds = 1 )
+
+ self.tag = Tag.create( self.object_id, self.notebook_id, self.user_id, self.name,
+ self.description, self.value )
+
+ def test_create( self ):
+ assert self.tag.object_id == self.object_id
+ assert self.tag.notebook_id == self.notebook_id
+ assert self.tag.user_id == self.user_id
+ assert self.tag.name == self.name
+ assert self.tag.description == self.description
+ assert self.tag.value == self.value
+
+ def test_to_dict( self ):
+ d = self.tag.to_dict()
+
+ assert d.get( "object_id" ) == self.object_id
+ assert datetime.now( tz = utc ) - d.get( "revision" ) < self.delta
+ assert d.get( "notebook_id" ) == self.notebook_id
+ assert d.get( "user_id" ) == self.user_id
+ assert d.get( "name" ) == self.name
+ assert d.get( "description" ) == self.description
+ assert d.get( "value" ) == self.value
diff --git a/setup.py b/setup.py
index 98f164a..ae20f1d 100644
--- a/setup.py
+++ b/setup.py
@@ -253,6 +253,7 @@ data_files = [
( "static/images/toolbar/small", files( "static/images/toolbar/small/*.*", excludes = [ "static/images/toolbar/small/*.xcf" ] ) ),
( "static/js", files( "static/js/*.*" ) ),
( "static/js", files( "static/js/*_LICENSE" ) ),
+ ( "model/delta", files( "model/delta/*.sqlite" ) ),
]
package_data = { ".": sum( [ pair[ 1 ] for pair in data_files ], [] ) }
diff --git a/static/html/support.html b/static/html/support.html
index b7c5564..ef69f34 100644
--- a/static/html/support.html
+++ b/static/html/support.html
@@ -20,14 +20,12 @@ The Luminotes user guide explains every feature of Luminotes in full detail,
and it even includes tips for organizing your wiki.
-
diff --git a/static/js/Wiki.js b/static/js/Wiki.js
index 8824442..3019486 100644
--- a/static/js/Wiki.js
+++ b/static/js/Wiki.js
@@ -1,4 +1,8 @@
IMAGE_DIR = "/static/images/";
+NOTEBOOK_READ_ONLY = 0;
+NOTEBOOK_READ_WRITE = 1;
+NOTEBOOK_READ_WRITE_FOR_OWN_NOTES = 2;
+
function Wiki( invoker ) {
this.next_id = null;
@@ -11,6 +15,7 @@ function Wiki( invoker ) {
this.open_editors = new Array(); // map of open notes: lowercase note title to editor
this.search_results_editor = null; // editor for display of search results
this.invoker = invoker;
+ this.user = evalJSON( getElement( "user" ).value );
this.rate_plan = evalJSON( getElement( "rate_plan" ).value );
this.yearly = evalJSON( getElement( "yearly" ).value );
this.storage_usage_high = false;
@@ -18,7 +23,6 @@ function Wiki( invoker ) {
this.invite_id = getElement( "invite_id" ).value;
this.after_login = getElement( "after_login" ).value;
this.signup_plan = getElement( "signup_plan" ).value;
- this.email_address = getElement( "email_address" ).value || "";
this.groups = evalJSON( getElement( "groups" ).value );
this.font_size = null;
this.small_toolbar = false;
@@ -48,7 +52,7 @@ function Wiki( invoker ) {
this.notebook = notebook;
}
- if ( this.notebook && this.notebook.read_write ) {
+ if ( this.notebook && this.notebook.read_write != NOTEBOOK_READ_ONLY ) {
var unsupported_agent = null;
var beta_agent = null;
@@ -76,7 +80,7 @@ function Wiki( invoker ) {
skip_empty_message
);
- this.display_storage_usage( evalJSON( getElement( "storage_bytes" ).value || "0" ) );
+ this.display_storage_usage( this.user.storage_bytes || "0" );
connect( this.invoker, "error_message", this, "display_error" );
connect( this.invoker, "message", this, "display_message" );
@@ -116,11 +120,11 @@ function Wiki( invoker ) {
}
var rename = evalJSON( getElement( "rename" ).value );
- if ( rename && this.notebook.read_write )
+ if ( rename && this.notebook.read_write == NOTEBOOK_READ_WRITE )
this.start_notebook_rename();
// if a notebook was just deleted, show a message with an undo button
- if ( deleted_id && this.notebook.read_write ) {
+ if ( deleted_id && this.notebook.read_write == NOTEBOOK_READ_WRITE ) {
var undo_button = createDOM( "input", {
"type": "button",
"class": "message_button",
@@ -276,7 +280,8 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
startup_note.deleted_from_id,
startup_note.revision,
startup_note.creation,
- this.notebook.read_write, false, focus
+ this.notebook.read_write, false, focus, null,
+ startup_note.user_id
);
if ( startup_note.title )
@@ -296,7 +301,8 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
note.deleted_from_id,
note.revision,
note.creation,
- this.notebook.read_write && note_read_write, false, focus
+ this.notebook.read_write != NOTEBOOK_READ_ONLY && note_read_write, false, focus, null,
+ note.user_id
);
focus = false;
}
@@ -308,7 +314,7 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
if ( empty_trash_link )
connect( empty_trash_link, "onclick", function ( event ) { self.delete_all_editors( event ); } );
- if ( this.notebook.read_write ) {
+ if ( this.notebook.read_write != NOTEBOOK_READ_ONLY ) {
connect( window, "onunload", function ( event ) { self.editor_focused( null, true ); } );
connect( "newNote", "onclick", this, "create_blank_editor" );
connect( "createLink", "onclick", this, "toggle_link_button" );
@@ -453,7 +459,7 @@ Wiki.prototype.create_blank_editor = function ( event ) {
}
}
- var editor = this.create_editor( undefined, undefined, undefined, undefined, undefined, this.notebook.read_write, true, true );
+ var editor = this.create_editor( undefined, undefined, undefined, undefined, undefined, this.notebook.read_write, true, true, null, this.user.object_id );
this.increment_total_notes_count();
this.blank_editor_id = editor.id;
signal( this, "note_added", editor );
@@ -693,6 +699,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
var actual_creation = result.note.creation;
var note_text = result.note.contents;
var deleted_from_id = result.note.deleted;
+ var user_id = result.note.user_id;
} else {
// if the title looks like a URL, then make it a link to an external site
if ( /^\w+:\/\//.test( note_title ) ) {
@@ -707,6 +714,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
var deleted_from_id = null;
var actual_revision = null;
var actual_creation = null;
+ var user_id = null;
this.increment_total_notes_count();
}
@@ -716,7 +724,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
var read_write = this.notebook.read_write;
var self = this;
- var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, actual_creation, read_write, true, false, position_after );
+ var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, actual_creation, read_write, true, false, position_after, user_id );
if ( !requested_revision )
connect( editor, "init_complete", function () { signal( self, "note_added", editor ); } );
id = editor.id;
@@ -726,10 +734,21 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
link.href = "/notebooks/" + this.notebook_id + "?note_id=" + id;
}
-Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, creation, read_write, highlight, focus, position_after ) {
+Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, creation, read_write, highlight, focus, position_after, user_id ) {
var self = this;
var dirty = false;
+ if ( read_write == NOTEBOOK_READ_ONLY )
+ read_write = false;
+ else if ( read_write == NOTEBOOK_READ_WRITE )
+ read_write = true;
+ else if ( read_write == NOTEBOOK_READ_WRITE_FOR_OWN_NOTES ) {
+ if ( user_id == this.user.object_id )
+ read_write = true;
+ else
+ read_write = false;
+ }
+
if ( isUndefinedOrNull( id ) ) {
if ( this.notebook.read_write ) {
id = this.next_id;
@@ -743,7 +762,7 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
}
// for read-only notes within read-write notebooks, tack the revision timestamp onto the start of the note text
- if ( !read_write && this.notebook.read_write && revision ) {
+ if ( !read_write && this.notebook.read_write == NOTEBOOK_READ_WRITE && revision ) {
var short_revision = this.brief_revision( revision );
var note_id = id.split( ' ' )[ 0 ];
note_text = 'Previous revision from ' + short_revision + '
' +
@@ -984,7 +1003,7 @@ Wiki.prototype.editor_mouse_hovered = function ( editor, target ) {
}
Wiki.prototype.key_pressed = function ( event ) {
- if ( !this.notebook.read_write )
+ if ( this.notebook.read_write == NOTEBOOK_READ_ONLY )
return;
var code = event.key().code;
@@ -1221,7 +1240,7 @@ Wiki.prototype.update_toolbar = function() {
var link = null;
// a read-only notebook doesn't have a visible toolbar
- if ( !this.notebook.read_write )
+ if ( this.notebook.read_write == NOTEBOOK_READ_ONLY )
return;
if ( this.focused_editor ) {
@@ -1332,7 +1351,7 @@ Wiki.prototype.hide_editor = function ( event, editor ) {
this.display_empty_message();
} else {
// before hiding an editor, save it
- if ( this.notebook.read_write && editor.read_write ) {
+ if ( this.notebook.read_write != NOTEBOOK_READ_ONLY && editor.read_write ) {
var self = this;
this.save_editor( editor, false, function () {
editor.shutdown();
@@ -1367,7 +1386,7 @@ Wiki.prototype.delete_editor = function ( event, editor ) {
var self = this;
this.save_editor( editor, false, function () {
- if ( self.notebook.read_write && editor.read_write ) {
+ if ( self.notebook.read_write != NOTEBOOK_READ_ONLY && editor.read_write ) {
self.invoker.invoke( "/notebooks/delete_note", "POST", {
"notebook_id": self.notebook_id,
"note_id": editor.id
@@ -1414,7 +1433,7 @@ Wiki.prototype.undelete_editor_via_trash = function ( event, editor ) {
if ( this.startup_notes[ editor.id ] )
delete this.startup_notes[ editor.id ];
- if ( this.notebook.read_write && editor.read_write ) {
+ if ( this.notebook.read_write != NOTEBOOK_READ_ONLY && editor.read_write ) {
var self = this;
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
"notebook_id": editor.deleted_from_id,
@@ -1437,7 +1456,7 @@ Wiki.prototype.undelete_editor_via_trash = function ( event, editor ) {
Wiki.prototype.undelete_editor_via_undo = function( event, editor, position_after ) {
if ( editor ) {
- if ( this.notebook.read_write && editor.read_write ) {
+ if ( this.notebook.read_write != NOTEBOOK_READ_ONLY && editor.read_write ) {
var self = this;
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
"notebook_id": this.notebook_id,
@@ -1458,7 +1477,7 @@ Wiki.prototype.undelete_editor_via_undo = function( event, editor, position_afte
}
Wiki.prototype.undelete_editor_via_undelete = function( event, note_id, position_after ) {
- if ( this.notebook.read_write ) {
+ if ( this.notebook.read_write != NOTEBOOK_READ_ONLY ) {
var self = this;
this.invoker.invoke( "/notebooks/undelete_note", "POST", {
"notebook_id": this.notebook_id,
@@ -1600,7 +1619,7 @@ Wiki.prototype.submit_form = function ( form ) {
}
} else if ( url == "/users/update_settings" ) {
callback = function ( result ) {
- self.email_address = result.email_address || "";
+ self.user.email_address = result.email_address || "";
self.display_message( "Your account settings have been updated." );
}
} else if ( url == "/users/signup_group_member" ) {
@@ -1996,7 +2015,7 @@ Wiki.prototype.display_settings = function () {
createDOM( "br", {} ),
createDOM( "input",
{ "type": "text", "name": "email_address", "id": "email_address", "class": "text_field",
- "size": "30", "maxlength": "60", "value": this.email_address || "" }
+ "size": "30", "maxlength": "60", "value": this.user.email_address || "" }
)
),
createDOM( "p", {},
@@ -2510,7 +2529,7 @@ Wiki.prototype.delete_all_editors = function ( event ) {
this.startup_notes = new Array();
- if ( this.notebook.read_write ) {
+ if ( this.notebook.read_write == NOTEBOOK_READ_WRITE ) {
var self = this;
this.invoker.invoke( "/notebooks/delete_all_notes", "POST", {
"notebook_id": this.notebook_id
diff --git a/tools/initdb.py b/tools/initdb.py
index be8a717..3b32c95 100644
--- a/tools/initdb.py
+++ b/tools/initdb.py
@@ -50,7 +50,7 @@ class Initializer( object ):
def create_main_notebook( self ):
# create the main notebook
main_notebook_id = self.database.next_id( Notebook )
- self.main_notebook = Notebook.create( main_notebook_id, u"Luminotes" )
+ self.main_notebook = Notebook.create( main_notebook_id, u"Luminotes", rank = 0 )
self.database.save( self.main_notebook, commit = False )
# no need to create default notes for the desktop version
diff --git a/tools/make_forum_thread.py b/tools/make_forum_thread.py
new file mode 100755
index 0000000..5c8819e
--- /dev/null
+++ b/tools/make_forum_thread.py
@@ -0,0 +1,113 @@
+#!/usr/bin/python2.4
+
+import os
+import os.path
+import sys
+import cherrypy
+from datetime import datetime
+from controller.Database import Database
+from model.Notebook import Notebook
+from model.Note import Note
+from model.User import User
+from model.Tag import Tag
+
+
+class Thread_maker( object ):
+ """
+ Create a thread for a new forum.
+ """
+ def __init__( self, database, forum_name ):
+ self.database = database
+ self.forum_name = forum_name
+
+ self.make_thread()
+ self.database.commit()
+
+ def make_thread( self ):
+ title = u"Welcome to the Luminotes %s forum!" % self.forum_name
+
+ # create a notebook thread to go in the forum
+ notebook_id = self.database.next_id( Notebook, commit = False )
+ thread_notebook = Notebook.create(
+ notebook_id,
+ title,
+ )
+ self.database.save( thread_notebook, commit = False )
+
+ anonymous = self.database.select_one( User, User.sql_load_by_username( u"anonymous" ) )
+
+ # add a single welcome note to the new thread
+ note_id = self.database.next_id( Note, commit = False )
+ note = Note.create(
+ note_id,
+ u"""
+ %s
You can discuss any Luminotes %s topics here. This is a public discussion
+ forum, so please keep that in mind when posting. And have fun.
+ """ % ( title, self.forum_name ),
+ notebook_id,
+ startup = True,
+ rank = 0,
+ user_id = anonymous.object_id,
+ creation = datetime.now(),
+ )
+ self.database.save( note, commit = False )
+
+ # load the forum tag, or create one if it doesn't exist
+ tag = self.database.select_one( Tag, Tag.sql_load_by_name( u"forum", user_id = anonymous.object_id ) )
+ if not tag:
+ tag_id = self.database.next_id( Tag, commit = False )
+ tag = Tag.create(
+ tag_id,
+ notebook_id = None, # this tag is not in the namespace of a single notebook
+ user_id = anonymous.object_id,
+ name = u"forum",
+ description = u"discussion forum threads"
+ )
+ self.database.save( tag, commit = False )
+
+ # associate the forum tag with the previously created notebook thread, and set that
+ # association's value to forum_name
+ self.database.execute(
+ anonymous.sql_save_notebook_tag( notebook_id, tag.object_id, value = self.forum_name ),
+ commit = False,
+ )
+
+ # give the anonymous user access to the new notebook thread
+ self.database.execute(
+ anonymous.sql_save_notebook( notebook_id, read_write = True, owner = False, own_notes_only = True ),
+ commit = False,
+ )
+
+
+def main( args ):
+ import cherrypy
+ from config import Common
+
+ cherrypy.config.update( Common.settings )
+ desktop = False
+
+ if args and "-d" in args:
+ from config import Development
+ settings = Development.settings
+ args.remove( "-d" )
+ elif args and "-l" in args:
+ from config import Desktop
+ settings = Desktop.settings
+ desktop = True
+ args.remove( "-l" )
+ else:
+ from config import Production
+ settings = Production.settings
+
+ cherrypy.config.update( settings )
+
+ database = Database(
+ host = cherrypy.config.configMap[ u"global" ].get( u"luminotes.db_host" ),
+ ssl_mode = cherrypy.config.configMap[ u"global" ].get( u"luminotes.db_ssl_mode" ),
+ data_dir = ".",
+ )
+ ranker = Thread_maker( database, *args )
+
+
+if __name__ == "__main__":
+ main( sys.argv[ 1: ] )
diff --git a/view/Forums_page.py b/view/Forums_page.py
index 117ed3b..2eb890f 100644
--- a/view/Forums_page.py
+++ b/view/Forums_page.py
@@ -3,7 +3,7 @@ from Tags import Div, Img, A, P, Span, I, Br
class Forums_page( Product_page ):
- def __init__( self, user, notebooks, first_notebook, login_url, logout_url, rate_plan ):
+ def __init__( self, user, notebooks, first_notebook, login_url, logout_url, rate_plan, groups ):
Product_page.__init__(
self,
user,
@@ -21,14 +21,15 @@ class Forums_page( Product_page ):
),
),
Div(
+ Span( A( u"general discussion", href = u"/forums/general" ), class_ = u"forum_title" ),
+ P(
+ u"""
+ Swap tips about making the most out of your personal wiki, and discuss your ideas for
+ new Luminotes features and enhancements.
+ """
+ ),
Span( A( u"technical support", href = u"/forums/support" ), class_ = u"forum_title" ),
P( u"Having a problem with your wiki? Something not working as expected? Ask about it here." ),
-
- Span( A( u"feature requests", href = u"/forums/features" ), class_ = u"forum_title" ),
- P( u"Discuss your ideas for new Luminotes features and enhancements." ),
-
- Span( A( u"general discussion", href = u"/forums/general" ), class_ = u"forum_title" ),
- P( u"Swap tips about making the most out of your personal wiki." ),
class_ = u"forums_text",
),
class_ = u"forums_area",
diff --git a/view/Link_area.py b/view/Link_area.py
index e2022a6..09445e1 100644
--- a/view/Link_area.py
+++ b/view/Link_area.py
@@ -1,12 +1,16 @@
from Tags import Div, P, Span, H4, A, Strong, Img, Input, Br
from Rounded_div import Rounded_div
from Search_form import Search_form
+from model.Notebook import Notebook
class Link_area( Div ):
def __init__( self, notebooks, notebook, parent_id, notebook_path, updates_path, user, rate_plan ):
linked_notebooks = [ nb for nb in notebooks if
- ( nb.read_write or not nb.name.startswith( u"Luminotes" ) ) and
+ (
+ nb.read_write == Notebook.READ_WRITE or
+ ( nb.read_write == Notebook.READ_ONLY and not nb.name.startswith( u"Luminotes" ) )
+ ) and
nb.name not in ( u"trash" ) and
nb.deleted is False
]
@@ -17,7 +21,7 @@ class Link_area( Div ):
Div(
H4(
u"this notebook",
- notebook.read_write and Input(
+ notebook.read_write != Notebook.READ_ONLY and Input(
type = u"button",
class_ = u"note_button small_text",
id = u"save_button",
@@ -60,7 +64,7 @@ class Link_area( Div ):
class_ = u"link_area_item",
) or None ),
- notebook.read_write and Div(
+ ( notebook.read_write != Notebook.READ_ONLY ) and Div(
A(
u"nothing but notes",
href = u"#",
@@ -70,7 +74,7 @@ class Link_area( Div ):
class_ = u"link_area_item",
) or None,
- ( not notebook.read_write and notebook.name != u"Luminotes" ) and Div(
+ ( notebook.read_write != Notebook.READ_WRITE and notebook.name != u"Luminotes" ) and Div(
A(
u"export",
href = u"#",
@@ -80,7 +84,7 @@ class Link_area( Div ):
class_ = u"link_area_item",
) or None,
- notebook.read_write and Span(
+ ( notebook.read_write == Notebook.READ_WRITE ) and Span(
Div(
A(
u"import",
diff --git a/view/Main_page.py b/view/Main_page.py
index afb7203..80d268c 100644
--- a/view/Main_page.py
+++ b/view/Main_page.py
@@ -9,6 +9,7 @@ from Toolbar import Toolbar
from Json import Json
from Rounded_div import Rounded_div
from config.Version import VERSION
+from model.Notebook import Notebook
class Main_page( Page ):
@@ -105,7 +106,7 @@ class Main_page( Page ):
except IOError:
pass
- if notebook.read_write is True:
+ if notebook.read_write == Notebook.READ_WRITE:
header_note_title = u"wiki"
else:
all_notes = startup_notes + notes
@@ -118,7 +119,7 @@ class Main_page( Page ):
"Luminotes privacy policy": "privacy",
}.get( header_note_title, header_note_title )
- own_notebooks = [ nb for nb in notebooks if nb.read_write is True ]
+ own_notebooks = [ nb for nb in notebooks if nb.read_write == Notebook.READ_WRITE ]
header_notebook = own_notebooks and own_notebooks[ 0 ] or notebook
Page.__init__(
@@ -133,7 +134,7 @@ class Main_page( Page ):
Script( type = u"text/javascript", src = u"/static/js/Invoker.js" ) or None,
Script( type = u"text/javascript", src = u"/static/js/Editor.js" ) or None,
Script( type = u"text/javascript", src = u"/static/js/Wiki.js" ) or None,
- Input( type = u"hidden", name = u"storage_bytes", id = u"storage_bytes", value = user.storage_bytes ),
+ Input( type = u"hidden", name = u"user", id = u"user", value = json( user ) ),
Input( type = u"hidden", name = u"rate_plan", id = u"rate_plan", value = json( rate_plan ) ),
Input( type = u"hidden", name = u"yearly", id = u"yearly", value = json( signup_yearly ) ),
Input( type = u"hidden", name = u"notebooks", id = u"notebooks", value = json( notebooks ) ),
@@ -148,7 +149,6 @@ class Main_page( Page ):
Input( type = u"hidden", name = u"invite_id", id = u"invite_id", value = invite_id ),
Input( type = u"hidden", name = u"after_login", id = u"after_login", value = after_login ),
Input( type = u"hidden", name = u"signup_plan", id = u"signup_plan", value = signup_plan ),
- Input( type = u"hidden", name = u"email_address", id = u"email_address", value = user.email_address ),
Input( type = u"hidden", name = u"groups", id = u"groups", value = json( groups ) ),
Div(
id = u"status_area",
@@ -158,7 +158,7 @@ class Main_page( Page ):
Div(
Note_tree_area(
Toolbar(
- hide_toolbar = parent_id or not notebook.read_write
+ hide_toolbar = parent_id or notebook.read_write == Notebook.READ_ONLY
),
notebook,
root_notes,
@@ -168,7 +168,7 @@ class Main_page( Page ):
id = u"left_area",
),
Div(
- notebook.read_write and Noscript(
+ ( notebook.read_write != Notebook.READ_ONLY ) and Noscript(
P( Strong(
u"""
Luminotes requires JavaScript to be enabled in your web browser in order to edit
@@ -181,7 +181,7 @@ class Main_page( Page ):
( notebook.name == u"Luminotes" and title == u"source code" ) and \
Strong( "%s %s" % ( notebook.name, VERSION ) ) or \
Span(
- ( notebook.name == u"trash" or not notebook.read_write ) \
+ ( notebook.name == u"trash" or notebook.read_write != Notebook.READ_WRITE ) \
and Strong( notebook.name ) \
or Span( Strong( notebook.name ), id = u"notebook_header_name", title = "Rename this notebook." ),
),
@@ -204,7 +204,7 @@ class Main_page( Page ):
Span( id = u"notes_top" ),
id = u"notes",
),
- notebook.read_write and Div(
+ ( notebook.read_write != Notebook.READ_ONLY ) and Div(
id = u"blank_note_stub",
class_ = u"blank_note_stub_hidden_border",
) or None,