witten
/
luminotes
Archived
1
0
Fork 0

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

This commit is contained in:
Dan Helfman 2008-10-24 11:51:19 -07:00
parent 86e5e38d69
commit 388f2fcb02
31 changed files with 1367 additions and 691 deletions

8
NEWS
View File

@ -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.

38
UPGRADE
View File

@ -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:

View File

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

View File

@ -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 )

View File

@ -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

View File

@ -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,14 +409,11 @@ 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 ) )
return dict(
@ -520,14 +512,11 @@ 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 ) )
return dict(
@ -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 )
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

View File

@ -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 )

View File

@ -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(

View File

@ -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()

View File

@ -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"<h3>new title</h3>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"<h3>new title</h3>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

View File

@ -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" ]

View File

@ -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"<h3>login</h3>",
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"<h3>hi</h3>",
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"<h3>hi from another user</h3>",
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"<h3>hi</h3>",
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"<h3>hi from another user</h3>",
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"<h3>hi</h3>",
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"<h3>hi from another user</h3>",
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

View File

@ -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()

View File

@ -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 ):

135
model/Tag.py Normal file
View File

@ -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 )

View File

@ -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 ):
"""

30
model/delta/1.5.5.sql Normal file
View File

@ -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';

37
model/delta/1.5.5.sqlite Normal file
View File

@ -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;

View File

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

View File

@ -1,5 +1,5 @@
--
-- PostgreSQL database dump
-- PostgreSQL database schema
--
SET client_encoding = 'UTF8';
@ -12,22 +12,10 @@ SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: drop_html_tags(text); Type: FUNCTION; Schema: public; Owner: luminotes
--
CREATE FUNCTION drop_html_tags(text) RETURNS text
AS $_$select regexp_replace( regexp_replace( $1, '</?(div|p|br|ul|ol|li|h3)( [^>]*?)?/?>', ' ', 'gi' ), '<[^>]+?>', '', 'g' );$_$
LANGUAGE sql;
ALTER FUNCTION public.drop_html_tags(text) OWNER TO luminotes;
--
-- Name: file; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE file (
id text NOT NULL,
revision timestamp with time zone,
@ -37,14 +25,30 @@ CREATE TABLE file (
size_bytes integer,
content_type text
);
ALTER TABLE public.file OWNER TO luminotes;
CREATE TABLE tag (
id text NOT NULL,
revision timestamp with time zone,
notebook_id text,
user_id text,
name text,
description text
);
ALTER TABLE public.tag OWNER TO luminotes;
CREATE TABLE tag_notebook (
notebook_id text,
tag_id text,
value text,
user_id text
);
--
-- Name: invite; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE public.tag_notebook OWNER TO luminotes;
CREATE TABLE tag_note (
note_id text,
tag_id text,
value text
);
ALTER TABLE public.tag_note OWNER TO luminotes;
CREATE TABLE invite (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@ -55,37 +59,16 @@ CREATE TABLE invite (
"owner" boolean,
redeemed_user_id text
);
ALTER TABLE public.invite OWNER TO luminotes;
--
-- Name: luminotes_group; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE luminotes_group (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
name text
);
ALTER TABLE public.luminotes_group OWNER TO luminotes;
--
-- Name: luminotes_group_current; Type: VIEW; Schema: public; Owner: luminotes
--
CREATE VIEW luminotes_group_current AS
SELECT luminotes_group.id, luminotes_group.revision, luminotes_group.name FROM luminotes_group WHERE (luminotes_group.revision IN (SELECT max(sub_group.revision) AS max FROM luminotes_group sub_group WHERE (sub_group.id = luminotes_group.id)));
ALTER TABLE public.luminotes_group_current OWNER TO luminotes;
--
-- Name: luminotes_user; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE luminotes_user (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@ -96,24 +79,10 @@ CREATE TABLE luminotes_user (
storage_bytes integer,
rate_plan integer
);
ALTER TABLE public.luminotes_user OWNER TO luminotes;
--
-- Name: luminotes_user_current; Type: VIEW; Schema: public; Owner: luminotes
--
CREATE VIEW luminotes_user_current AS
SELECT luminotes_user.id, luminotes_user.revision, luminotes_user.username, luminotes_user.salt, luminotes_user.password_hash, luminotes_user.email_address, luminotes_user.storage_bytes, luminotes_user.rate_plan FROM luminotes_user WHERE (luminotes_user.revision IN (SELECT max(sub_user.revision) AS max FROM luminotes_user sub_user WHERE (sub_user.id = luminotes_user.id)));
ALTER TABLE public.luminotes_user_current OWNER TO luminotes;
--
-- Name: note; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE note (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@ -126,24 +95,10 @@ CREATE TABLE note (
search tsvector,
user_id text
);
ALTER TABLE public.note OWNER TO luminotes;
--
-- Name: note_current; Type: VIEW; Schema: public; Owner: luminotes
--
CREATE VIEW note_current AS
SELECT note.id, note.revision, note.title, note.contents, note.notebook_id, note.startup, note.deleted_from_id, note.rank, note.search, note.user_id FROM note WHERE (note.revision IN (SELECT max(sub_note.revision) AS max FROM note sub_note WHERE (sub_note.id = note.id)));
ALTER TABLE public.note_current OWNER TO luminotes;
--
-- Name: notebook; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE notebook (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
@ -152,239 +107,106 @@ CREATE TABLE notebook (
deleted boolean DEFAULT false,
user_id text
);
ALTER TABLE public.notebook OWNER TO luminotes;
--
-- Name: notebook_current; Type: VIEW; Schema: public; Owner: luminotes
--
CREATE VIEW notebook_current AS
SELECT notebook.id, notebook.revision, notebook.name, notebook.trash_id, notebook.deleted, notebook.user_id FROM notebook WHERE (notebook.revision IN (SELECT max(sub_notebook.revision) AS max FROM notebook sub_notebook WHERE (sub_notebook.id = notebook.id)));
ALTER TABLE public.notebook_current OWNER TO luminotes;
--
-- Name: password_reset; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE password_reset (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
email_address text,
redeemed boolean
);
ALTER TABLE public.password_reset OWNER TO luminotes;
-- Name: download_access; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE download_access (
id text NOT NULL,
revision timestamp with time zone NOT NULL,
item_number text,
transaction_id text
);
ALTER TABLE public.download_access OWNER TO luminotes;
--
--
-- Name: user_group; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE user_group (
user_id text NOT NULL,
group_id text NOT NULL,
"admin" boolean DEFAULT false
);
ALTER TABLE public.user_group OWNER TO luminotes;
--
-- Name: user_notebook; Type: TABLE; Schema: public; Owner: luminotes; Tablespace:
--
CREATE TABLE user_notebook (
user_id text NOT NULL,
notebook_id text NOT NULL,
read_write boolean DEFAULT false,
"owner" boolean DEFAULT false,
rank numeric
rank numeric,
own_notes_only boolean DEFAULT false
);
ALTER TABLE public.user_notebook OWNER TO luminotes;
-- Name: file_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY file
ADD CONSTRAINT file_pkey PRIMARY KEY (id);
ALTER TABLE ONLY tag
ADD CONSTRAINT tag_pkey PRIMARY KEY (id);
--
-- Name: invite_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY tag_notebook
ADD CONSTRAINT tag_notebook_pkey PRIMARY KEY (user_id, notebook_id, tag_id);
ALTER TABLE ONLY tag_note
ADD CONSTRAINT tag_note_pkey PRIMARY KEY (note_id, tag_id);
ALTER TABLE ONLY invite
ADD CONSTRAINT invite_pkey PRIMARY KEY (id);
--
-- Name: luminotes_user_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY luminotes_user
ADD CONSTRAINT luminotes_user_pkey PRIMARY KEY (id, revision);
--
-- Name: note_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY note
ADD CONSTRAINT note_pkey PRIMARY KEY (id, revision);
--
-- Name: notebook_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY notebook
ADD CONSTRAINT notebook_pkey PRIMARY KEY (id, revision);
--
-- Name: password_reset_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY password_reset
ADD CONSTRAINT password_reset_pkey PRIMARY KEY (id);
--
-- Name: download_access_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY download_access
ADD CONSTRAINT download_access_pkey PRIMARY KEY (id);
--
-- Name: user_notebook_pkey; Type: CONSTRAINT; Schema: public; Owner: luminotes; Tablespace:
--
ALTER TABLE ONLY user_notebook
ADD CONSTRAINT user_notebook_pkey PRIMARY KEY (user_id, notebook_id);
--
-- Name: file_note_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX file_note_id_index ON file USING btree (note_id);
--
-- Name: file_notebook_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX file_notebook_id_index ON file USING btree (notebook_id);
CREATE INDEX tag_notebook_id_index ON tag USING btree (notebook_id);
--
-- Name: luminotes_group_pkey; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX tag_user_id_index ON tag USING btree (user_id);
CREATE INDEX luminotes_group_pkey ON luminotes_group USING btree (id, revision);
--
-- Name: luminotes_user_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX luminotes_user_email_address_index ON luminotes_user USING btree (email_address);
--
-- Name: luminotes_user_username_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX luminotes_user_username_index ON luminotes_user USING btree (username);
--
-- Name: note_notebook_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX note_notebook_id_index ON note USING btree (notebook_id);
--
-- Name: note_notebook_id_startup_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX note_notebook_id_startup_index ON note USING btree (notebook_id, startup);
--
-- Name: note_notebook_id_title_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX note_notebook_id_title_index ON note USING btree (notebook_id, md5(title));
--
-- Name: password_reset_email_address_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX password_reset_email_address_index ON password_reset USING btree (email_address);
--
-- Name: download_access_transaction_id_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX download_access_transaction_id_index ON download_access USING btree (transaction_id);
--
-- Name: search_index; Type: INDEX; Schema: public; Owner: luminotes; Tablespace:
--
CREATE INDEX search_index ON note USING gist (search);
--
-- Name: search_update; Type: TRIGGER; Schema: public; Owner: luminotes
--
CREATE TRIGGER search_update
BEFORE INSERT OR UPDATE ON note
FOR EACH ROW
EXECUTE PROCEDURE tsearch2('search', 'drop_html_tags', 'title', 'contents');
--
-- Name: public; Type: ACL; Schema: -; Owner: postgres
--
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON SCHEMA public FROM postgres;
GRANT ALL ON SCHEMA public TO postgres;
GRANT ALL ON SCHEMA public TO PUBLIC;
--
-- PostgreSQL database dump complete
--

View File

@ -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

View File

@ -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"<h3>title</h3>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

36
model/test/Test_tag.py Normal file
View File

@ -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

View File

@ -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 ], [] ) }

View File

@ -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.
</p>
<!--
<h4><!a href="/forums/" target="_top">discussion forums</a></h4>
<h4><a href="/forums/" target="_top">discussion forums</a></h4>
<p>
In the Luminotes discussion forums, you can ask about Luminotes features and
chat with your fellow Luminoters.
</p>
-->
<h4><a href="/blog" target="_top">blog</a></h4>

View File

@ -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 = '<p>Previous revision from ' + short_revision + '</p>' +
@ -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

View File

@ -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

113
tools/make_forum_thread.py Executable file
View File

@ -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"""
<h3>%s</h3> 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: ] )

View File

@ -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",

View File

@ -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",

View File

@ -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,