From 388f2fcb021df73ae512714b44a63e8499eb5081 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 24 Oct 2008 11:51:19 -0700 Subject: [PATCH] Foundational work for both tags and discussion forums. Should have checked this in in smaller pieces. --- NEWS | 8 + UPGRADE | 38 +++- config/Version.py | 2 +- controller/Files.py | 22 +- controller/Forums.py | 62 +++++- controller/Notebooks.py | 192 +++++++----------- controller/Root.py | 4 +- controller/Users.py | 71 +++++-- controller/test/Test_database.py | 2 +- controller/test/Test_notebooks.py | 171 ++++++++++++---- controller/test/Test_root.py | 12 +- controller/test/Test_users.py | 320 +++++++++++++++++++++++------- luminotes.py | 7 + model/Notebook.py | 72 ++++++- model/Tag.py | 135 +++++++++++++ model/User.py | 67 +++++-- model/delta/1.5.5.sql | 30 +++ model/delta/1.5.5.sqlite | 37 ++++ model/drop.sql | 3 + model/schema.sql | 246 ++++------------------- model/schema.sqlite | 169 ++++------------ model/test/Test_notebook.py | 124 +++++++++++- model/test/Test_tag.py | 36 ++++ setup.py | 1 + static/html/support.html | 4 +- static/js/Wiki.js | 63 ++++-- tools/initdb.py | 2 +- tools/make_forum_thread.py | 113 +++++++++++ view/Forums_page.py | 15 +- view/Link_area.py | 14 +- view/Main_page.py | 16 +- 31 files changed, 1367 insertions(+), 691 deletions(-) create mode 100644 model/Tag.py create mode 100644 model/delta/1.5.5.sql create mode 100644 model/delta/1.5.5.sqlite create mode 100644 model/test/Test_tag.py create mode 100755 tools/make_forum_thread.py 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, ']*?)?/?>', ' ', '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.

-

blog

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,