diff --git a/controller/Notebooks.py b/controller/Notebooks.py index 18f919b..435318f 100644 --- a/controller/Notebooks.py +++ b/controller/Notebooks.py @@ -46,13 +46,15 @@ class Notebooks( object ): self.__users = users @expose( view = Main_page ) + @grab_user_id @validate( notebook_id = Valid_id(), note_id = Valid_id(), parent_id = Valid_id(), revision = Valid_revision(), + user_id = Valid_id( none_okay = True ), ) - def default( self, notebook_id, note_id = None, parent_id = None, revision = None ): + def default( self, notebook_id, note_id = None, parent_id = None, revision = None, user_id = None ): """ Provide the information necessary to display the page for a particular notebook. If a particular note id is given without a revision, then the most recent version of that note is @@ -69,26 +71,18 @@ class Notebooks( object ): @rtype: unicode @return: rendered HTML page """ - return dict( - notebook_id = notebook_id, - note_id = note_id, - parent_id = parent_id, - revision = revision, - ) + result = self.__users.current( user_id ) + result.update( self.contents( notebook_id, note_id, revision, user_id ) ) + result[ "parent_id" ] = parent_id + if revision: + result[ "note_read_write" ] = False + + return result - @expose( view = Json ) - @strongly_expire - @grab_user_id - @validate( - notebook_id = Valid_id(), - note_id = Valid_id( none_okay = True ), - revision = Valid_revision( none_okay = True ), - user_id = Valid_id( none_okay = True ), - ) def contents( self, notebook_id, note_id = None, revision = None, user_id = None ): """ - Return the information on particular notebook, including the contents of its startup notes. - Optionally include the contents of a single requested note as well. + Return the startup notes for the given notebook. Optionally include a single requested note as + well. @type notebook_id: unicode @param notebook_id: id of notebook to return @@ -97,9 +91,13 @@ class Notebooks( object ): @type revision: unicode or NoneType @param revision: revision timestamp of the provided note (optional) @type user_id: unicode or NoneType - @param user_id: id of current logged-in user (if any), determined by @grab_user_id - @rtype: json dict - @return: { 'notebook': notebookdict, 'note': notedict or None } + @param user_id: id of current logged-in user (if any) + @rtype: dict + @return: { + 'notebook': notebook, + 'startup_notes': notelist, + 'note': note or None, + } @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 """ @@ -108,17 +106,18 @@ class Notebooks( object ): notebook = self.__database.load( Notebook, notebook_id ) + if notebook is None: + raise Access_error() + if not self.__users.check_access( user_id, notebook_id, read_write = True ): notebook.read_write = False - if notebook is None: - note = None - elif note_id == u"blank": - note = Note.create( note_id ) - else: + if note_id: note = self.__database.load( Note, note_id, revision ) if note and note.notebook_id != notebook_id: raise Access_error() + else: + note = None startup_notes = self.__database.select_many( Note, notebook.sql_load_startup_notes() ) diff --git a/controller/Root.py b/controller/Root.py index 1338b3e..cfee3e8 100644 --- a/controller/Root.py +++ b/controller/Root.py @@ -1,9 +1,10 @@ import cherrypy from Expose import expose +from Expire import strongly_expire from Validate import validate from Notebooks import Notebooks -from Users import Users +from Users import Users, grab_user_id from Database import Valid_id from model.Note import Note from view.Main_page import Main_page @@ -52,7 +53,12 @@ class Root( object ): ) @expose( view = Main_page ) - def index( self ): + @strongly_expire + @grab_user_id + @validate( + user_id = Valid_id( none_okay = True ), + ) + def index( self, user_id ): """ Provide the information necessary to display the web site's front page, potentially performing a redirect to the https version of the page. @@ -64,7 +70,11 @@ class Root( object ): if cherrypy.session.get( "user_id" ) and https_url and cherrypy.request.remote_addr != https_proxy_ip: return dict( redirect = https_url ) - return dict() + result = self.__users.current( user_id ) + first_notebook_id = result[ u"notebooks" ][ 0 ].object_id + result.update( self.__notebooks.contents( first_notebook_id, user_id = user_id ) ) + + return result # TODO: move this method to controller.Notebooks, and maybe give it a more sensible name @expose( view = Json ) diff --git a/controller/Users.py b/controller/Users.py index fabc3ae..e4aedaa 100644 --- a/controller/Users.py +++ b/controller/Users.py @@ -68,6 +68,17 @@ class Password_reset_error( Exception ): ) +class Access_error( Exception ): + def __init__( self, message ): + Exception.__init__( self, message ) + self.__message = message + + def to_dict( self ): + return dict( + error = self.__message + ) + + def grab_user_id( function ): """ A decorator to grab the current logged in user id from the cherrypy session and pass it as a @@ -329,29 +340,19 @@ class Users( object ): deauthenticated = True, ) - @expose( view = Json ) - @strongly_expire - @grab_user_id - @validate( - include_startup_notes = Valid_bool(), - user_id = Valid_id( none_okay = True ), - ) - def current( self, include_startup_notes, user_id ): + def current( self, user_id ): """ Return information on the currently logged-in user. If not logged in, default to the anonymous user. - @type include_startup_notes: bool - @param include_startup_notes: True to return startup notes for the first notebook @type user_id: unicode - @param user_id: id of current logged-in user (if any), determined by @grab_user_id + @param user_id: id of current logged-in user (if any) @rtype: json dict @return: { - 'user': userdict or None, - 'notebooks': notebooksdict, - 'startup_notes': noteslist, - 'http_url': url, + 'user': user or None, + 'notebooks': notebookslist, 'login_url': url, + 'logout_url': url, 'rate_plan': rateplandict, } @raise Validation_error: one of the arguments is invalid @@ -364,37 +365,27 @@ class Users( object ): user = anonymous if not user or not anonymous: - return dict( - user = None, - notebooks = None, - http_url = u"", - ) + raise Access_error( u"Sorry, you don't have access to do that." ) # in addition to this user's own notebooks, add to that list the anonymous user's notebooks login_url = None notebooks = self.__database.select_many( Notebook, anonymous.sql_load_notebooks() ) - if user_id: + if user_id and user_id != anonymous.object_id: notebooks += self.__database.select_many( Notebook, user.sql_load_notebooks() ) # if the user is not logged in, return a login URL else: - if len( notebooks ) > 0: + if len( notebooks ) > 0 and notebooks[ 0 ]: main_notebook = notebooks[ 0 ] login_note = self.__database.select_one( Note, main_notebook.sql_load_note_by_title( u"login" ) ) if login_note: login_url = "%s/notebooks/%s?note_id=%s" % ( self.__https_url, main_notebook.object_id, login_note.object_id ) - if include_startup_notes and len( notebooks ) > 0: - startup_notes = self.__database.select_many( Note, notebooks[ 0 ].sql_load_startup_notes() ) - else: - startup_notes = [] - return dict( user = user, notebooks = notebooks, - startup_notes = startup_notes, - http_url = self.__http_url, login_url = login_url, + logout_url = self.__https_url + u"/", rate_plan = ( user.rate_plan < len( self.__rate_plans ) ) and self.__rate_plans[ user.rate_plan ] or {}, ) @@ -452,7 +443,7 @@ class Users( object ): # check if the given user has access to this notebook user = self.__database.load( User, user_id ) - if user and self.__database.select_one( bool, user.sql_has_access( notebook_id ) ): + if user and self.__database.select_one( bool, user.sql_has_access( notebook_id, read_write ) ): return True return False @@ -551,12 +542,18 @@ class Users( object ): if len( matching_users ) == 0: raise Password_reset_error( u"There are no Luminotes users with the email address %s" % password_reset.email_address ) - return dict( + result = self.current( anonymous.object_id ) + result[ "notebook" ] = main_notebook + result[ "startup_notes" ] = self.__database.select_many( Note, main_notebook.sql_load_startup_notes() ) + result[ "note_read_write" ] = False + result[ "note" ] = Note.create( + object_id = u"password_reset", + contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ), notebook_id = main_notebook.object_id, - note_id = u"blank", - note_contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ), ) + return result + @expose( view = Json ) def reset_password( self, password_reset_id, reset_button, **new_passwords ): """ diff --git a/controller/test/Stub_database.py b/controller/test/Stub_database.py index 52b0d30..60ef9b5 100644 --- a/controller/test/Stub_database.py +++ b/controller/test/Stub_database.py @@ -6,9 +6,11 @@ class Stub_database( object ): # map of object id to list of saved objects (presumably in increasing order of revisions) self.objects = {} self.user_notebook = {} # map of user_id to ( notebook_id, read_write ) + self.last_saved_obj = None self.__next_id = 0 def save( self, obj, commit = False ): + self.last_saved_obj = obj if obj.object_id in self.objects: self.objects[ obj.object_id ].append( copy( obj ) ) else: diff --git a/controller/test/Test_controller.py b/controller/test/Test_controller.py index 74f8482..d42278c 100644 --- a/controller/test/Test_controller.py +++ b/controller/test/Test_controller.py @@ -29,7 +29,7 @@ class Test_controller( object ): notebooks = [] notebook_tuples = database.user_notebook.get( self.object_id ) - if not notebook_tuples: return None + if not notebook_tuples: return [] for notebook_tuple in notebook_tuples: ( notebook_id, read_write ) = notebook_tuple diff --git a/controller/test/Test_notebooks.py b/controller/test/Test_notebooks.py index c3add00..b99f0d4 100644 --- a/controller/test/Test_notebooks.py +++ b/controller/test/Test_notebooks.py @@ -1,10 +1,12 @@ import cherrypy import cgi +from nose.tools import raises from urllib import quote from Test_controller import Test_controller from model.Notebook import Notebook from model.Note import Note from model.User import User +from controller.Notebooks import Access_error class Test_notebooks( Test_controller ): @@ -53,53 +55,118 @@ class Test_notebooks( Test_controller ): self.database.save( self.anonymous, commit = False ) self.database.execute( self.user.sql_save_notebook( self.anon_notebook.object_id, read_write = False ) ) - def test_default( self ): - result = self.http_get( "/notebooks/%s" % self.notebook.object_id ) + def test_default_without_login( self ): + result = self.http_get( + "/notebooks/%s" % self.notebook.object_id, + ) - assert result.get( u"notebook_id" ) == self.notebook.object_id + assert u"access" in result[ u"error" ] + user = self.database.load( User, self.user.object_id ) + assert user.storage_bytes == 0 + + def test_default( self ): + self.login() + + result = self.http_get( + "/notebooks/%s" % self.notebook.object_id, + session_id = self.session_id, + ) + + assert result.get( u"user" ).object_id == self.user.object_id + assert len( result.get( u"notebooks" ) ) == 3 + 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 len( result.get( u"startup_notes" ) ) == 1 + assert result.get( u"note" ) is None + assert result.get( u"parent_id" ) == None + assert result.get( u"note_read_write" ) in ( None, True ) + user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 def test_default_with_note( self ): - result = self.http_get( "/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ) ) + self.login() + + result = self.http_get( + "/notebooks/%s?note_id=%s" % ( self.notebook.object_id, self.note.object_id ), + session_id = self.session_id, + ) - assert result.get( u"notebook_id" ) == self.notebook.object_id - assert result.get( u"note_id" ) == self.note.object_id + assert result.get( u"user" ).object_id == self.user.object_id + assert len( result.get( u"notebooks" ) ) == 3 + 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 len( result.get( u"startup_notes" ) ) == 1 + assert result.get( u"note" ).object_id == self.note.object_id + assert result.get( u"parent_id" ) == None + assert result.get( u"note_read_write" ) in ( None, True ) + user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 def test_default_with_note_and_revision( self ): - result = self.http_get( "/notebooks/%s?note_id=%s&revision=%s" % ( - self.notebook.object_id, - self.note.object_id, - quote( unicode( self.note.revision ) ), - ) ) + self.login() + + result = self.http_get( + "/notebooks/%s?note_id=%s&revision=%s" % ( + self.notebook.object_id, + self.note.object_id, + quote( unicode( self.note.revision ) ), + ), + session_id = self.session_id, + ) - assert result.get( u"notebook_id" ) == self.notebook.object_id - assert result.get( u"note_id" ) == self.note.object_id - assert result.get( u"revision" ) == unicode( self.note.revision ) + assert result.get( u"user" ).object_id == self.user.object_id + assert len( result.get( u"notebooks" ) ) == 3 + 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 len( result.get( u"startup_notes" ) ) == 1 + assert result.get( u"note" ).object_id == self.note.object_id + assert result.get( u"note" ).revision == self.note.revision + assert result.get( u"parent_id" ) == None + assert result.get( u"note_read_write" ) == False + user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 def test_default_with_parent( self ): - parent_id = "foo" - result = self.http_get( "/notebooks/%s?parent_id=%s" % ( self.notebook.object_id, parent_id ) ) + self.login() + + parent_id = u"foo" + result = self.http_get( + "/notebooks/%s?parent_id=%s" % ( self.notebook.object_id, parent_id ), + session_id = self.session_id, + ) - assert result.get( u"notebook_id" ) == self.notebook.object_id + assert result.get( u"user" ).object_id == self.user.object_id + assert len( result.get( u"notebooks" ) ) == 3 + 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 len( result.get( u"startup_notes" ) ) == 1 + assert result.get( u"note" ) is None assert result.get( u"parent_id" ) == parent_id + assert result.get( u"note_read_write" ) in ( None, True ) + user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 def test_contents( self ): - self.login() - - result = self.http_get( - "/notebooks/contents?notebook_id=%s" % self.notebook.object_id, - session_id = self.session_id, + result = cherrypy.root.notebooks.contents( + notebook_id = self.notebook.object_id, + user_id = self.user.object_id, ) notebook = result[ "notebook" ] startup_notes = result[ "startup_notes" ] + assert result[ "note" ] == None assert notebook.object_id == self.notebook.object_id assert notebook.read_write == True @@ -109,11 +176,10 @@ class Test_notebooks( Test_controller ): assert user.storage_bytes == 0 def test_contents_with_note( self ): - self.login() - - result = self.http_get( - "/notebooks/contents?notebook_id=%s¬e_id=%s" % ( self.notebook.object_id, self.note.object_id ), - session_id = self.session_id, + result = cherrypy.root.notebooks.contents( + notebook_id = self.notebook.object_id, + note_id = self.note.object_id, + user_id = self.user.object_id, ) notebook = result[ "notebook" ] @@ -131,16 +197,13 @@ class Test_notebooks( Test_controller ): assert user.storage_bytes == 0 def test_contents_with_note_and_revision( self ): - self.login() - - result = self.http_get( - "/notebooks/contents?notebook_id=%s¬e_id=%s&revision=%s" % ( - self.notebook.object_id, - self.note.object_id, - quote( unicode( self.note.revision ) ), - ), - session_id = self.session_id, + result = cherrypy.root.notebooks.contents( + notebook_id = self.notebook.object_id, + note_id = self.note.object_id, + revision = unicode( self.note.revision ), + user_id = self.user.object_id, ) + self.login() notebook = result[ "notebook" ] startup_notes = result[ "startup_notes" ] @@ -153,54 +216,39 @@ class Test_notebooks( Test_controller ): note = result[ "note" ] assert note.object_id == self.note.object_id + assert note.revision == self.note.revision user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 - def test_contents_with_blank_note( self ): - self.login() - - result = self.http_get( - "/notebooks/contents?notebook_id=%s¬e_id=blank" % self.notebook.object_id , - session_id = self.session_id, + @raises( Access_error ) + def test_contents_without_user_id( self ): + result = cherrypy.root.notebooks.contents( + notebook_id = self.notebook.object_id, ) - notebook = result[ "notebook" ] - startup_notes = result[ "startup_notes" ] - - assert notebook.object_id == self.notebook.object_id - assert notebook.read_write == True - assert len( startup_notes ) == 1 - assert startup_notes[ 0 ].object_id == self.note.object_id - - note = result[ "note" ] - - assert note.object_id == u"blank" - assert note.contents == None - assert note.title == None - assert note.deleted_from_id == None - user = self.database.load( User, self.user.object_id ) - assert user.storage_bytes == 0 - - def test_contents_without_login( self ): - result = self.http_get( - "/notebooks/contents?notebook_id=%s" % self.notebook.object_id, - session_id = self.session_id, + @raises( Access_error ) + def test_contents_with_incorrect_user_id( self ): + result = cherrypy.root.notebooks.contents( + notebook_id = self.notebook.object_id, + user_id = self.anonymous.object_id, ) - assert result.get( "error" ) - user = self.database.load( User, self.user.object_id ) - assert user.storage_bytes == 0 + @raises( Access_error ) + def test_contents_with_unknown_notebook_id( self ): + result = cherrypy.root.notebooks.contents( + notebook_id = self.unknown_notebook_id, + user_id = self.user.object_id, + ) def test_contents_with_read_only_notebook( self ): - self.login() - - result = self.http_get( - "/notebooks/contents?notebook_id=%s" % self.anon_notebook.object_id, - session_id = self.session_id, + result = cherrypy.root.notebooks.contents( + notebook_id = self.anon_notebook.object_id, + user_id = self.user.object_id, ) notebook = result[ "notebook" ] startup_notes = result[ "startup_notes" ] + assert result[ "note" ] == None assert notebook.object_id == self.anon_notebook.object_id assert notebook.read_write == False diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py index eca491d..590aa28 100644 --- a/controller/test/Test_users.py +++ b/controller/test/Test_users.py @@ -2,6 +2,7 @@ import re import cherrypy import smtplib from pytz import utc +from nose.tools import raises from datetime import datetime, timedelta from nose.tools import raises from Test_controller import Test_controller @@ -10,6 +11,7 @@ from model.User import User from model.Notebook import Notebook from model.Note import Note from model.Password_reset import Password_reset +from controller.Users import Access_error class Test_users( Test_controller ): @@ -80,7 +82,7 @@ class Test_users( Test_controller ): assert result[ u"redirect" ].startswith( u"/notebooks/" ) - def test_current_after_signup( self, include_startup_notes = False ): + def test_current_after_signup( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, password = self.new_password, @@ -92,12 +94,14 @@ class Test_users( Test_controller ): new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] - result = self.http_get( - "/users/current?include_startup_notes=%s" % include_startup_notes, - session_id = session_id, - ) + user = self.database.last_saved_obj + assert isinstance( user, User ) + result = cherrypy.root.users.current( user.object_id ) + assert result[ u"user" ].object_id == user.object_id assert result[ u"user" ].username == self.new_username + assert result[ u"user" ].email_address == self.new_email_address + notebooks = result[ u"notebooks" ] notebook = notebooks[ 0 ] assert notebook.object_id == self.anon_notebook.object_id @@ -120,22 +124,13 @@ class Test_users( Test_controller ): assert notebook.trash_id == None assert notebook.read_write == True - startup_notes = result[ "startup_notes" ] - if include_startup_notes: - assert len( startup_notes ) == 1 - assert startup_notes[ 0 ].object_id == self.startup_note.object_id - assert startup_notes[ 0 ].title == self.startup_note.title - assert startup_notes[ 0 ].contents == self.startup_note.contents - else: - assert startup_notes == [] + assert result.get( u"login_url" ) is None + assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 - def test_current_with_startup_notes_after_signup( self ): - self.test_current_after_signup( include_startup_notes = True ) - def test_signup_with_different_passwords( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, @@ -152,18 +147,20 @@ class Test_users( Test_controller ): assert result[ u"redirect" ].startswith( u"/notebooks/" ) - def test_current_after_demo( self, include_startup_notes = False ): + def test_current_after_demo( self ): result = self.http_post( "/users/demo", dict() ) session_id = result[ u"session_id" ] new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] - result = self.http_get( - "/users/current?include_startup_notes=%s" % include_startup_notes, - session_id = session_id, - ) + user = self.database.last_saved_obj + assert isinstance( user, User ) + result = cherrypy.root.users.current( user.object_id ) + + assert result[ u"user" ].object_id == user.object_id + assert result[ u"user" ].username is None + assert result[ u"user" ].email_address is None - assert result[ u"user" ].username == None notebooks = result[ u"notebooks" ] assert len( notebooks ) == 3 notebook = notebooks[ 0 ] @@ -187,34 +184,25 @@ class Test_users( Test_controller ): assert notebook.trash_id == None assert notebook.read_write == True - startup_notes = result[ "startup_notes" ] - if include_startup_notes: - assert len( startup_notes ) == 1 - assert startup_notes[ 0 ].object_id == self.startup_note.object_id - assert startup_notes[ 0 ].title == self.startup_note.title - assert startup_notes[ 0 ].contents == self.startup_note.contents - else: - assert startup_notes == [] + assert result.get( u"login_url" ) is None + assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" rate_plan = result[ u"rate_plan" ] assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 - def test_current_with_startup_notes_after_demo( self ): - self.test_current_after_demo( include_startup_notes = True ) - - def test_current_after_demo_twice( self, include_startup_notes = False ): + def test_current_after_demo_twice( self ): result = self.http_post( "/users/demo", dict() ) session_id = result[ u"session_id" ] new_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ] - result = self.http_get( - "/users/current?include_startup_notes=%s" % include_startup_notes, - session_id = session_id, - ) + user = self.database.last_saved_obj + assert isinstance( user, User ) + result = cherrypy.root.users.current( user.object_id ) user_id = result[ u"user" ].object_id + assert user_id == user.object_id # request a demo for a second time result = self.http_post( "/users/demo", dict(), session_id = session_id ) @@ -224,10 +212,7 @@ class Test_users( Test_controller ): assert notebook_id_again == new_notebook_id - result = self.http_get( - "/users/current?include_startup_notes=%s" % include_startup_notes, - session_id = session_id, - ) + result = cherrypy.root.users.current( user_id ) user_id_again = result[ u"user" ].object_id @@ -235,9 +220,6 @@ class Test_users( Test_controller ): # should just use the same guest user with the same notebook assert user_id_again == user_id - def test_current_with_startup_notes_after_demo_twice( self ): - self.test_current_after_demo_twice( include_startup_notes = True ) - def test_login( self ): result = self.http_post( "/users/login", dict( username = self.username, @@ -270,18 +252,8 @@ class Test_users( Test_controller ): assert result[ u"redirect" ] == self.settings[ u"global" ].get( u"luminotes.http_url" ) + u"/" - def test_current_after_login( self, include_startup_notes = False ): - result = self.http_post( "/users/login", dict( - username = self.username, - password = self.password, - login_button = u"login", - ) ) - session_id = result[ u"session_id" ] - - result = self.http_get( - "/users/current?include_startup_notes=%s" % include_startup_notes, - session_id = session_id, - ) + def test_current( self ): + result = cherrypy.root.users.current( self.user.object_id ) assert result[ u"user" ] assert result[ u"user" ].object_id == self.user.object_id @@ -293,32 +265,22 @@ class Test_users( Test_controller ): assert result[ u"notebooks" ][ 1 ].read_write == True assert result[ u"notebooks" ][ 2 ].object_id == self.notebooks[ 1 ].object_id assert result[ u"notebooks" ][ 2 ].read_write == True - assert result[ u"http_url" ] == self.settings[ u"global" ].get( u"luminotes.http_url" ) - assert result[ u"login_url" ] == None + assert result[ u"login_url" ] is None + assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" - startup_notes = result[ "startup_notes" ] - if include_startup_notes: - assert len( startup_notes ) == 1 - assert startup_notes[ 0 ].object_id == self.startup_note.object_id - assert startup_notes[ 0 ].title == self.startup_note.title - assert startup_notes[ 0 ].contents == self.startup_note.contents - else: - assert startup_notes == [] + rate_plan = result[ u"rate_plan" ] + assert rate_plan + assert rate_plan[ u"name" ] == u"super" + assert rate_plan[ u"storage_quota_bytes" ] == 1337 - def test_current_with_startup_notes_after_login( self ): - self.test_current_after_login( include_startup_notes = True ) - - def test_current_without_login( self, include_startup_notes = False ): - result = self.http_get( - "/users/current?include_startup_notes=%s" % include_startup_notes, - ) + def test_current_anonymous( self ): + result = cherrypy.root.users.current( self.anonymous.object_id ) assert result[ u"user" ].username == "anonymous" 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"http_url" ] == self.settings[ u"global" ].get( u"luminotes.http_url" ) login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) ) assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % ( @@ -326,18 +288,12 @@ class Test_users( Test_controller ): self.anon_notebook.object_id, login_note.object_id, ) + assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" - startup_notes = result[ "startup_notes" ] - if include_startup_notes: - assert len( startup_notes ) == 1 - assert startup_notes[ 0 ].object_id == self.startup_note.object_id - assert startup_notes[ 0 ].title == self.startup_note.title - assert startup_notes[ 0 ].contents == self.startup_note.contents - else: - assert startup_notes == [] - - def test_current_with_startup_notes_without_login( self ): - self.test_current_without_login( include_startup_notes = True ) + rate_plan = result[ u"rate_plan" ] + assert rate_plan + assert rate_plan[ u"name" ] == u"super" + assert rate_plan[ u"storage_quota_bytes" ] == 1337 def test_update_storage( self ): previous_revision = self.user.revision @@ -409,11 +365,36 @@ class Test_users( Test_controller ): result = self.http_get( "/users/redeem_reset/%s" % password_reset_id ) - assert result[ u"notebook_id" ] == self.anon_notebook.object_id - assert result[ u"note_id" ] - assert u"password reset" in result[ u"note_contents" ] - assert self.user.username in result[ u"note_contents" ] - assert self.user2.username in result[ u"note_contents" ] + assert result[ u"user" ].username == "anonymous" + 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 + + login_note = self.database.select_one( Note, self.anon_notebook.sql_load_note_by_title( u"login" ) ) + assert result[ u"login_url" ] == u"%s/notebooks/%s?note_id=%s" % ( + self.settings[ u"global" ][ u"luminotes.https_url" ], + self.anon_notebook.object_id, + login_note.object_id, + ) + assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" + + rate_plan = result[ u"rate_plan" ] + assert rate_plan + assert rate_plan[ u"name" ] == u"super" + assert rate_plan[ u"storage_quota_bytes" ] == 1337 + + assert result[ u"notebook" ].object_id == self.anon_notebook.object_id + assert len( result[ u"startup_notes" ] ) == 1 + assert result[ u"startup_notes" ][ 0 ].object_id == self.startup_note.object_id + assert result[ u"startup_notes" ][ 0 ].title == self.startup_note.title + assert result[ u"startup_notes" ][ 0 ].contents == self.startup_note.contents + assert result[ u"note_read_write" ] is False + assert result[ u"note" ].title == u"complete your password reset" + assert result[ u"note" ].notebook_id == self.anon_notebook.object_id + assert u"password reset" in result[ u"note" ].contents + assert self.user.username in result[ u"note" ].contents + assert self.user2.username in result[ u"note" ].contents def test_redeem_reset_unknown( self ): password_reset_id = u"unknownresetid" diff --git a/static/css/style.css b/static/css/style.css index a00f705..9d56483 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -13,14 +13,6 @@ a:hover { color: #ff6600; } -noscript { - text-align: left; -} - -noscript h3 { - margin-bottom: 0.5em; -} - ul li { margin-top: 0.5em; } @@ -206,6 +198,16 @@ ol li { -webkit-border-radius: 0.5em 0 0 0; } +#static_notes { + text-align: left; + margin: 1em; + line-height: 140%; +} + +#static_notes h3 { + margin-bottom: 0.5em; +} + #notes { text-align: left; margin-top: 1em; @@ -405,3 +407,18 @@ ol li { -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em; } + +.button { + border-style: outset; + border-width: 0px; + background-color: #d0e0f0; + font-size: 100%; + outline: none; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; +} + +.button:hover { + background-color: #ffcc66; +} + diff --git a/static/html/no javascript.html b/static/html/no javascript.html deleted file mode 100644 index bb92bc4..0000000 --- a/static/html/no javascript.html +++ /dev/null @@ -1,4 +0,0 @@ -

Luminotes requires JavaScript

- -So if you'd like to check out this site any further, please enable JavaScript -in your web browser, and then reload this page. Sorry for the inconvenience. diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 3b14692..4244380 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -1,3 +1,4 @@ + function Wiki( invoker ) { this.next_id = null; this.focused_editor = null; @@ -5,104 +6,52 @@ function Wiki( invoker ) { this.notebook = null; this.notebook_id = getElement( "notebook_id" ).value; this.parent_id = getElement( "parent_id" ).value; // id of the notebook containing this one - this.read_write = false; this.startup_notes = new Array(); // map of startup notes: note id to bool this.open_editors = new Array(); // map of open notes: note title to editor this.all_notes_editor = null; // editor for display of list of all notes this.search_results_editor = null; // editor for display of search results this.invoker = invoker; - this.rate_plan = null; + this.rate_plan = evalJSON( getElement( "rate_plan" ).value ); this.storage_usage_high = false; + // grab the current notebook from the list of available notebooks + var notebooks = evalJSON( getElement( "notebooks" ).value ); + for ( var i in notebooks ) { + if ( notebooks[ i ].object_id == this.notebook_id ) { + this.notebook = notebooks[ i ] + break; + } + } + + // populate the wiki with startup notes + this.populate( + evalJSON( getElement( "startup_notes" ).value || "null" ), + evalJSON( getElement( "note" ).value || "null" ), + evalJSON( getElement( "note_read_write" ).value || "true" ) + ); + + this.display_storage_usage( evalJSON( getElement( "storage_bytes" ).value || "0" ) ); + connect( this.invoker, "error_message", this, "display_error" ); connect( this.invoker, "message", this, "display_message" ); connect( "search_form", "onsubmit", this, "search" ); connect( "html", "onclick", this, "background_clicked" ); - // get info on the requested notebook (if any) var self = this; - if ( this.notebook_id ) { - this.invoker.invoke( - "/notebooks/contents", "GET", { - "notebook_id": this.notebook_id, - "note_id": getElement( "note_id" ).value, - "revision": getElement( "revision" ).value - }, - function( result ) { self.populate( result ); } - ); - var include_startup_notes = false; - } else { - var include_startup_notes = true; + var logout_link = getElement( "logout_link" ); + if ( logout_link ) { + connect( "logout_link", "onclick", function ( event ) { + self.save_editor( null, true ); + self.invoker.invoke( "/users/logout", "POST" ); + event.stop(); + } ); } - - // get info on the current user (logged-in or anonymous) - this.invoker.invoke( "/users/current", "GET", { - "include_startup_notes": include_startup_notes - }, - function( result ) { self.display_user( result ); } - ); } Wiki.prototype.update_next_id = function ( result ) { this.next_id = result.next_id; } -Wiki.prototype.display_user = function ( result ) { - // if no notebook id was requested, then just display the user's default notebook - if ( !this.notebook_id ) { - this.notebook_id = result.notebooks[ 0 ].object_id; - this.populate( { "notebook" : result.notebooks[ 0 ], "startup_notes": result.startup_notes } ); - } - - var user_span = createDOM( "span" ); - replaceChildNodes( "user_area", user_span ); - - // if not logged in, display a login link - if ( result.user.username == "anonymous" && result.login_url ) { - appendChildNodes( user_span, createDOM( "a", { "href": result.login_url, "id": "login_link" }, "login" ) ); - return; - } - - // display links for current notebook and a list of all notebooks that the user has access to - var notebooks_span = createDOM( "span" ); - replaceChildNodes( "notebooks_area", notebooks_span ); - - appendChildNodes( notebooks_span, createDOM( "h4", "notebooks" ) ); - - for ( var i in result.notebooks ) { - var notebook = result.notebooks[ i ]; - - if ( notebook.name == "Luminotes" || notebook.name == "trash" ) - continue; - - var div_class = "link_area_item"; - if ( notebook.object_id == this.notebook_id ) - div_class += " current_notebook_name"; - - appendChildNodes( notebooks_span, createDOM( "div", { - "class": div_class - }, createDOM( "a", { - "href": "/notebooks/" + notebook.object_id, - "id": "notebook_" + notebook.object_id - }, notebook.name ) ) ); - } - - this.rate_plan = result.rate_plan; - this.display_storage_usage( result.user.storage_bytes ); - - // display the name of the logged in user and a logout link - appendChildNodes( user_span, "logged in as " + ( result.user.username || "a guest" ) ); - appendChildNodes( user_span, " | " ); - appendChildNodes( user_span, createDOM( "a", { "href": result.http_url + "/", "id": "logout_link" }, "logout" ) ); - - var self = this; - connect( "logout_link", "onclick", function ( event ) { - self.save_editor( null, true ); - self.invoker.invoke( "/users/logout", "POST" ); - event.stop(); - } ); -} - Wiki.prototype.display_storage_usage = function( storage_bytes ) { if ( !storage_bytes ) return; @@ -113,7 +62,10 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) { return Math.round( storage_bytes / MEGABYTE ); } - var quota_bytes = this.rate_plan.storage_quota_bytes || 0; + var quota_bytes = this.rate_plan.storage_quota_bytes; + if ( !quota_bytes ) + return; + var usage_percent = Math.round( storage_bytes / quota_bytes * 100.0 ); if ( usage_percent > 90 ) { @@ -136,68 +88,49 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) { ); } -Wiki.prototype.populate = function ( result ) { - this.notebook = result.notebook; +Wiki.prototype.populate = function ( startup_notes, note, note_read_write ) { + // create an editor for each startup note in the received notebook, focusing the first one + var focus = true; + for ( var i in startup_notes ) { + var startup_note = startup_notes[ i ]; + this.startup_notes[ startup_note.object_id ] = true; + + // don't actually create an editor if a particular note was provided in the result + if ( !note ) { + var editor = this.create_editor( + startup_note.object_id, + // grab this note's contents from the static