From ad58956f34989b89cc0895d8ccb7d997a2f5ec45 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Tue, 30 Oct 2007 23:05:46 +0000 Subject: [PATCH] Some early work in support of a reverse chronological order view of a notebook, useful for a blog. --- controller/Expose.py | 1 + controller/Notebooks.py | 39 ++++++++- controller/Root.py | 31 ++++++- controller/Users.py | 15 ++-- controller/Validate.py | 20 +++++ controller/test/Test_controller.py | 18 +++++ controller/test/Test_notebooks.py | 125 ++++++++++++++++++++++++++--- controller/test/Test_root.py | 23 +++++- controller/test/Test_users.py | 59 +++++++------- model/Note.py | 13 ++- model/Notebook.py | 21 +++++ model/test/Test_note.py | 10 ++- static/js/Wiki.js | 38 ++++++--- view/Link_area.py | 6 +- view/Main_page.py | 20 ++--- 15 files changed, 359 insertions(+), 80 deletions(-) diff --git a/controller/Expose.py b/controller/Expose.py index 7b83051..214b2a0 100644 --- a/controller/Expose.py +++ b/controller/Expose.py @@ -74,6 +74,7 @@ def expose( view = None, rss = None ): return unicode( view_override( **result ) ) except: if redirect is None: + print result raise # if that doesn't work, and there's a redirect, then redirect diff --git a/controller/Notebooks.py b/controller/Notebooks.py index 7ee4ed3..233962c 100644 --- a/controller/Notebooks.py +++ b/controller/Notebooks.py @@ -68,6 +68,8 @@ class Notebooks( object ): @param parent_id: id of parent notebook to this notebook (optional) @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) @rtype: unicode @return: rendered HTML page """ @@ -97,7 +99,7 @@ class Notebooks( object ): 'notebook': notebook, 'startup_notes': notelist, 'total_notes_count': notecount, - 'note': note or None, + 'notes': notelist, } @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 @@ -130,7 +132,7 @@ class Notebooks( object ): notebook = notebook, startup_notes = startup_notes, total_notes_count = total_notes_count, - note = note, + notes = note and [ note ] or [], ) @expose( view = Json ) @@ -693,3 +695,36 @@ class Notebooks( object ): notebook_name = notebook.name, notes = startup_notes + other_notes, ) + + def load_recent_notes( self, notebook_id, start = 0, count = 10, user_id = None ): + """ + Provide the information necessary to display the page for a particular notebook's most recent + notes. + + @type notebook_id: unicode + @param notebook_id: id of the notebook to display + @type start: unicode or NoneType + @param start: index of recent note to start with (defaults to 0, the most recent note) + @type count: int or NoneType + @param count: number of recent notes to display (defaults to 10 notes) + @type user_id: unicode or NoneType + @param user_id: id of current logged-in user (if any) + @rtype: dict + @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 ) + + if notebook is None: + raise Access_error() + + recent_notes = self.__database.select_many( Note, notebook.sql_load_recent_notes( start, count ) ) + + result = self.__users.current( user_id ) + result.update( self.contents( notebook_id, user_id = user_id ) ) + result[ u"notes" ] = recent_notes + + return result diff --git a/controller/Root.py b/controller/Root.py index 97cd0d3..bb67ed4 100644 --- a/controller/Root.py +++ b/controller/Root.py @@ -2,7 +2,7 @@ import cherrypy from Expose import expose from Expire import strongly_expire -from Validate import validate +from Validate import validate, Valid_int from Notebooks import Notebooks from Users import Users, grab_user_id from Database import Valid_id @@ -106,11 +106,36 @@ class Root( object ): return dict( redirect = https_url ) 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 ) ) + main_notebooks = [ nb for nb in result[ "notebooks" ] if nb.name == u"Luminotes" ] + result.update( self.__notebooks.contents( main_notebooks[ 0 ].object_id, user_id = user_id ) ) return result + @expose( view = Main_page ) + @grab_user_id + @validate( + start = Valid_int( min = 0 ), + count = Valid_int( min = 1, max = 50 ), + user_id = Valid_id( none_okay = True ), + ) + def blog( self, start = 0, count = 10, user_id = None ): + """ + Provide the information necessary to display the blog notebook with notes in reverse + chronological order. + + @type start: unicode or NoneType + @param start: index of recent note to start with (defaults to 0, the most recent note) + @type count: int or NoneType + @param count: number of recent notes to display (defaults to 10 notes) + @rtype: unicode + @return: rendered HTML page + @raise Validation_error: one of the arguments is invalid + """ + result = self.__users.current( user_id = None ) + blog_notebooks = [ nb for nb in result[ "notebooks" ] if nb.name == u"Luminotes blog" ] + + return self.__notebooks.load_recent_notes( blog_notebooks[ 0 ].object_id, start, count, user_id ) + # TODO: move this method to controller.Notebooks, and maybe give it a more sensible name @expose( view = Json ) def next_id( self ): diff --git a/controller/Users.py b/controller/Users.py index 42c117d..6cd7b31 100644 --- a/controller/Users.py +++ b/controller/Users.py @@ -370,21 +370,22 @@ class Users( object ): # 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() ) + anon_notebooks = self.__database.select_many( Notebook, anonymous.sql_load_notebooks() ) if user_id and user_id != anonymous.object_id: - notebooks += self.__database.select_many( Notebook, user.sql_load_notebooks() ) + 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 and notebooks[ 0 ]: - main_notebook = notebooks[ 0 ] + notebooks = [] + if len( anon_notebooks ) > 0 and anon_notebooks[ 0 ]: + main_notebook = anon_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 ) return dict( user = user, - notebooks = notebooks, + notebooks = notebooks + anon_notebooks, 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 {}, @@ -548,11 +549,11 @@ class Users( object ): result[ "startup_notes" ] = self.__database.select_many( Note, main_notebook.sql_load_startup_notes() ) result[ "total_notes_count" ] = self.__database.select_one( Note, main_notebook.sql_count_notes() ) result[ "note_read_write" ] = False - result[ "note" ] = Note.create( + result[ "notes" ] = [ Note.create( object_id = u"password_reset", contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ), notebook_id = main_notebook.object_id, - ) + ) ] return result diff --git a/controller/Validate.py b/controller/Validate.py index 5f0963b..e136b6c 100644 --- a/controller/Validate.py +++ b/controller/Validate.py @@ -134,6 +134,26 @@ class Valid_bool( object ): raise ValueError() +class Valid_int( object ): + """ + Validator for an integer value. + """ + def __init__( self, min = None, max = None ): + self.min = min + self.max = max + self.message = None + + def __call__( self, value ): + value = int( value ) + + if self.min is not None and value < min: + self.message = "is too small" + if self.max is not None and value > max: + self.message = "is too large" + + return value + + def validate( **expected ): """ validate() can be used to require that the arguments of the decorated method successfully pass diff --git a/controller/test/Test_controller.py b/controller/test/Test_controller.py index 7bdf860..ed5d57a 100644 --- a/controller/test/Test_controller.py +++ b/controller/test/Test_controller.py @@ -4,6 +4,7 @@ from Stub_view import Stub_view from config import Common from datetime import datetime from StringIO import StringIO +from copy import copy class Test_controller( object ): @@ -128,6 +129,23 @@ class Test_controller( object ): Notebook.sql_load_startup_notes = lambda self: \ lambda database: sql_load_startup_notes( self, database ) + def sql_load_recent_notes( self, database, start, count ): + notes = [] + + for ( object_id, obj_list ) in database.objects.items(): + obj = obj_list[ -1 ] + if isinstance( obj, Note ) and obj.notebook_id == self.object_id: + obj = copy( obj ) + obj._Note__creation = database.objects[ object_id ][ 0 ].revision + notes.append( obj ) + + notes.sort( lambda a, b: -cmp( a.creation, b.creation ) ) + notes = notes[ start : start + count ] + return notes + + Notebook.sql_load_recent_notes = lambda self, start = 0, count = 10: \ + lambda database: sql_load_recent_notes( self, database, start, count ) + def sql_load_note_by_title( self, title, database ): notes = [] diff --git a/controller/test/Test_notebooks.py b/controller/test/Test_notebooks.py index 193759c..3453610 100644 --- a/controller/test/Test_notebooks.py +++ b/controller/test/Test_notebooks.py @@ -80,7 +80,7 @@ class Test_notebooks( Test_controller ): assert result.get( u"notebook" ).object_id == self.notebook.object_id assert len( result.get( u"startup_notes" ) ) == 1 assert result[ "total_notes_count" ] == 2 - assert result.get( u"note" ) is None + assert result.get( u"notes" ) == [] assert result.get( u"parent_id" ) == None assert result.get( u"note_read_write" ) in ( None, True ) @@ -103,7 +103,10 @@ class Test_notebooks( Test_controller ): assert result.get( u"notebook" ).object_id == self.notebook.object_id assert len( result.get( u"startup_notes" ) ) == 1 assert result[ "total_notes_count" ] == 2 - assert result.get( u"note" ).object_id == self.note.object_id + + assert result.get( "notes" ) + assert len( result.get( "notes" ) ) == 1 + assert result.get( u"notes" )[ 0 ].object_id == self.note.object_id assert result.get( u"parent_id" ) == None assert result.get( u"note_read_write" ) in ( None, True ) @@ -130,8 +133,11 @@ class Test_notebooks( Test_controller ): assert result.get( u"notebook" ).object_id == self.notebook.object_id assert len( result.get( u"startup_notes" ) ) == 1 assert result[ "total_notes_count" ] == 2 - assert result.get( u"note" ).object_id == self.note.object_id - assert result.get( u"note" ).revision == self.note.revision + + assert result.get( "notes" ) + assert len( result.get( "notes" ) ) == 1 + assert result.get( u"notes" )[ 0 ].object_id == self.note.object_id + assert result.get( u"notes" )[ 0 ].revision == self.note.revision assert result.get( u"parent_id" ) == None assert result.get( u"note_read_write" ) == False @@ -155,7 +161,7 @@ class Test_notebooks( Test_controller ): assert result.get( u"notebook" ).object_id == self.notebook.object_id assert len( result.get( u"startup_notes" ) ) == 1 assert result[ "total_notes_count" ] == 2 - assert result.get( u"note" ) is None + assert result.get( u"notes" ) == [] assert result.get( u"parent_id" ) == parent_id assert result.get( u"note_read_write" ) in ( None, True ) @@ -171,7 +177,7 @@ class Test_notebooks( Test_controller ): notebook = result[ "notebook" ] startup_notes = result[ "startup_notes" ] assert result[ "total_notes_count" ] == 2 - assert result[ "note" ] == None + assert result[ "notes" ] == [] assert notebook.object_id == self.notebook.object_id assert notebook.read_write == True @@ -196,8 +202,11 @@ class Test_notebooks( Test_controller ): assert len( startup_notes ) == 1 assert startup_notes[ 0 ].object_id == self.note.object_id - note = result[ "note" ] + notes = result[ "notes" ] + assert notes + assert len( notes ) == 1 + note = notes[ 0 ] assert note.object_id == self.note.object_id user = self.database.load( User, self.user.object_id ) assert user.storage_bytes == 0 @@ -220,8 +229,11 @@ class Test_notebooks( Test_controller ): assert len( startup_notes ) == 1 assert startup_notes[ 0 ].object_id == self.note.object_id - note = result[ "note" ] + notes = result[ "notes" ] + assert notes + assert len( notes ) == 1 + note = notes[ 0 ] assert note.object_id == self.note.object_id assert note.revision == self.note.revision user = self.database.load( User, self.user.object_id ) @@ -255,7 +267,7 @@ class Test_notebooks( Test_controller ): notebook = result[ "notebook" ] startup_notes = result[ "startup_notes" ] - assert result[ "note" ] == None + assert result[ "notes" ] == [] assert result[ "total_notes_count" ] == 0 assert notebook.object_id == self.anon_notebook.object_id @@ -1604,6 +1616,101 @@ class Test_notebooks( Test_controller ): assert result.get( "error" ) + def test_recent_notes( self ): + result = cherrypy.root.notebooks.load_recent_notes( + self.notebook.object_id, + user_id = self.user.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[ "total_notes_count" ] == 2 + + notes = result.get( u"notes" ) + assert notes + assert len( notes ) == 2 + assert notes[ 0 ].object_id == self.note2.object_id + assert notes[ 1 ].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_recent_notes_with_start( self ): + result = cherrypy.root.notebooks.load_recent_notes( + self.notebook.object_id, + start = 1, + user_id = self.user.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[ "total_notes_count" ] == 2 + + notes = result.get( u"notes" ) + assert notes + assert len( notes ) == 1 + assert notes[ 0 ].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_recent_notes_with_count( self ): + result = cherrypy.root.notebooks.load_recent_notes( + self.notebook.object_id, + count = 1, + user_id = self.user.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[ "total_notes_count" ] == 2 + + notes = result.get( u"notes" ) + assert notes + assert len( notes ) == 1 + assert notes[ 0 ].object_id == self.note2.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 + + @raises( Access_error ) + def test_recent_notes_with_unknown_notebok( self ): + result = cherrypy.root.notebooks.load_recent_notes( + self.unknown_notebook_id, + user_id = self.user.object_id, + ) + + @raises( Access_error ) + def test_recent_notes_with_incorrect_user( self ): + result = cherrypy.root.notebooks.load_recent_notes( + self.notebook.object_id, + user_id = self.anonymous.object_id, + ) + def login( self ): result = self.http_post( "/users/login", dict( username = self.username, diff --git a/controller/test/Test_root.py b/controller/test/Test_root.py index 9d16b88..be1df63 100644 --- a/controller/test/Test_root.py +++ b/controller/test/Test_root.py @@ -13,7 +13,7 @@ class Test_root( Test_controller ): self.notebook = Notebook.create( self.database.next_id( Notebook ), u"my notebook" ) self.database.save( self.notebook ) - self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"anon notebook" ) + self.anon_notebook = Notebook.create( self.database.next_id( Notebook ), u"Luminotes" ) self.database.save( self.anon_notebook ) self.anon_note = Note.create( self.database.next_id( Note ), u"

my note

", @@ -21,6 +21,14 @@ class Test_root( Test_controller ): ) self.database.save( self.anon_note ) + self.blog_notebook = Notebook.create( self.database.next_id( Notebook ), u"Luminotes blog" ) + self.database.save( self.blog_notebook ) + self.blog_note = Note.create( + self.database.next_id( Note ), u"

my blog entry

", + notebook_id = self.blog_notebook.object_id, + ) + self.database.save( self.blog_note ) + self.username = u"mulder" self.password = u"trustno1" self.email_address = u"outthere@example.com" @@ -34,6 +42,7 @@ 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 ) ) + self.database.execute( self.anonymous.sql_save_notebook( self.blog_notebook.object_id ) ) def test_index( self ): result = self.http_get( "/" ) @@ -68,8 +77,9 @@ class Test_root( Test_controller ): ) assert result - assert result[ u"note" ] - assert result[ u"note" ].object_id == self.anon_note.object_id + assert result[ u"notes" ] + assert len( result[ u"notes" ] ) == 1 + assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id def test_default_with_unknown_note( self ): result = self.http_get( @@ -99,6 +109,13 @@ class Test_root( Test_controller ): assert result.get( "redirect" ) assert result.get( "redirect" ).startswith( "https://" ) + def test_blog( self ): + result = self.http_get( + "/blog", + ) + + assert result + def test_next_id( self ): result = self.http_get( "/next_id" ) diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py index acb2714..5e5c8a7 100644 --- a/controller/test/Test_users.py +++ b/controller/test/Test_users.py @@ -104,26 +104,26 @@ class Test_users( Test_controller ): notebooks = result[ u"notebooks" ] notebook = notebooks[ 0 ] - assert notebook.object_id == self.anon_notebook.object_id - assert notebook.revision == self.anon_notebook.revision - assert notebook.name == self.anon_notebook.name - assert notebook.trash_id == None - assert notebook.read_write == False - - notebook = notebooks[ 1 ] assert notebook.object_id == new_notebook_id assert notebook.revision assert notebook.name == u"my notebook" assert notebook.trash_id assert notebook.read_write == True - notebook = notebooks[ 2 ] - assert notebook.object_id == notebooks[ 1 ].trash_id + notebook = notebooks[ 1 ] + assert notebook.object_id == notebooks[ 0 ].trash_id assert notebook.revision assert notebook.name == u"trash" assert notebook.trash_id == None assert notebook.read_write == True + notebook = notebooks[ 2 ] + assert notebook.object_id == self.anon_notebook.object_id + 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 result.get( u"login_url" ) is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" @@ -164,26 +164,26 @@ class Test_users( Test_controller ): notebooks = result[ u"notebooks" ] assert len( notebooks ) == 3 notebook = notebooks[ 0 ] - assert notebook.object_id == self.anon_notebook.object_id - assert notebook.revision == self.anon_notebook.revision - assert notebook.name == self.anon_notebook.name - assert notebook.trash_id == None - assert notebook.read_write == False - - notebook = notebooks[ 1 ] assert notebook.object_id == new_notebook_id assert notebook.revision assert notebook.name == u"my notebook" assert notebook.trash_id assert notebook.read_write == True - notebook = notebooks[ 2 ] - assert notebook.object_id == notebooks[ 1 ].trash_id + notebook = notebooks[ 1 ] + assert notebook.object_id == notebooks[ 0 ].trash_id assert notebook.revision assert notebook.name == u"trash" assert notebook.trash_id == None assert notebook.read_write == True + notebook = notebooks[ 2 ] + assert notebook.object_id == self.anon_notebook.object_id + 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 result.get( u"login_url" ) is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" @@ -259,12 +259,12 @@ class Test_users( Test_controller ): assert result[ u"user" ].object_id == self.user.object_id assert result[ u"user" ].username == self.user.username assert len( result[ u"notebooks" ] ) == 3 - assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id - assert result[ u"notebooks" ][ 0 ].read_write == False - assert result[ u"notebooks" ][ 1 ].object_id == self.notebooks[ 0 ].object_id + assert result[ u"notebooks" ][ 0 ].object_id == self.notebooks[ 0 ].object_id + assert result[ u"notebooks" ][ 0 ].read_write == True + assert result[ u"notebooks" ][ 1 ].object_id == self.notebooks[ 1 ].object_id 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"notebooks" ][ 2 ].object_id == self.anon_notebook.object_id + assert result[ u"notebooks" ][ 2 ].read_write == False assert result[ u"login_url" ] is None assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" @@ -390,11 +390,14 @@ class Test_users( Test_controller ): 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 + + assert result[ u"notes" ] + assert len( result[ u"notes" ] ) == 1 + assert result[ u"notes" ][ 0 ].title == u"complete your password reset" + assert result[ u"notes" ][ 0 ].notebook_id == self.anon_notebook.object_id + assert u"password reset" in result[ u"notes" ][ 0 ].contents + assert self.user.username in result[ u"notes" ][ 0 ].contents + assert self.user2.username in result[ u"notes" ][ 0 ].contents def test_redeem_reset_unknown( self ): password_reset_id = u"unknownresetid" diff --git a/model/Note.py b/model/Note.py index bf35210..0d7d495 100644 --- a/model/Note.py +++ b/model/Note.py @@ -10,7 +10,7 @@ class Note( Persistent ): TITLE_PATTERN = re.compile( u"

(.*?)

", flags = re.IGNORECASE ) def __init__( self, object_id, revision = None, title = None, contents = None, notebook_id = None, - startup = None, deleted_from_id = None, rank = None ): + startup = None, deleted_from_id = None, rank = None, creation = None ): """ Create a new note with the given id and contents. @@ -30,6 +30,8 @@ class Note( Persistent ): @param deleted_from_id: id of the notebook that this note was deleted from (optional) @type rank: float or NoneType @param rank: indicates numeric ordering of this note in relation to other startup notes + @type creation: datetime or NoneType + @param creation: creation timestamp of the object (optional, defaults to None) @rtype: Note @return: newly constructed note """ @@ -40,9 +42,10 @@ class Note( Persistent ): self.__startup = startup or False self.__deleted_from_id = deleted_from_id self.__rank = rank + self.__creation = creation @staticmethod - def create( object_id, contents = None, notebook_id = None, startup = None, rank = None ): + def create( object_id, contents = None, notebook_id = None, startup = None, rank = None, creation = None ): """ Convenience constructor for creating a new note. @@ -56,10 +59,12 @@ class Note( Persistent ): @param startup: whether this note should be displayed upon startup (optional, defaults to False) @type rank: float or NoneType @param rank: indicates numeric ordering of this note in relation to other startup notes + @type creation: datetime or NoneType + @param creation: creation timestamp of the object (optional, defaults to None) @rtype: Note @return: newly constructed note """ - note = Note( object_id, notebook_id = notebook_id, startup = startup, rank = rank ) + note = Note( object_id, notebook_id = notebook_id, startup = startup, rank = rank, creation = creation ) note.contents = contents return note @@ -138,6 +143,7 @@ class Note( Persistent ): contents = self.__contents, title = self.__title, deleted_from_id = self.__deleted_from_id, + creation = self.__creation, ) ) return d @@ -148,3 +154,4 @@ class Note( Persistent ): startup = property( lambda self: self.__startup, __set_startup ) deleted_from_id = property( lambda self: self.__deleted_from_id, __set_deleted_from_id ) rank = property( lambda self: self.__rank, __set_rank ) + creation = property( lambda self: self.__creation ) diff --git a/model/Notebook.py b/model/Notebook.py index b2f49ce..287800e 100644 --- a/model/Notebook.py +++ b/model/Notebook.py @@ -92,6 +92,27 @@ class Notebook( Persistent ): """ return "select * from note_current where notebook_id = %s and startup = 't' order by rank;" % quote( self.object_id ) + def sql_load_recent_notes( self, start = 0, count = 10 ): + """ + Return a SQL string to load a list of the most recently created notes within this notebook. + + @type start: int or NoneType + @param start: index of recent note to start with (defaults to 0, the most recent note) + @type count: int or NoneType + @param count: number of recent notes to return (defaults to 10 notes) + """ + return \ + """ + select + note_current.*, note_creation.revision as creation + from + note_current, + ( select id, min( revision ) as revision from note where notebook_id = %s group by id ) as note_creation + where + notebook_id = %s and note_current.id = note_creation.id + offset %d limit %d; + """ % ( quote( self.object_id ), quote( self.object_id ), start, count ) + def sql_load_note_by_id( self, note_id ): """ Return a SQL string to load a particular note within this notebook by the note's id. diff --git a/model/test/Test_note.py b/model/test/Test_note.py index 98ec339..2226084 100644 --- a/model/test/Test_note.py +++ b/model/test/Test_note.py @@ -11,9 +11,10 @@ class Test_note( object ): self.notebook_id = u"18" self.startup = False self.rank = 17.5 + self.creation = datetime.now() self.delta = timedelta( seconds = 1 ) - self.note = Note.create( self.object_id, self.contents, self.notebook_id, self.startup, self.rank ) + self.note = Note.create( self.object_id, self.contents, self.notebook_id, self.startup, self.rank, self.creation ) def test_create( self ): assert self.note.object_id == self.object_id @@ -24,6 +25,7 @@ class Test_note( object ): assert self.note.startup == self.startup assert self.note.deleted_from_id == None assert self.note.rank == self.rank + assert self.note.creation == self.creation def test_set_contents( self ): new_title = u"new title" @@ -39,6 +41,7 @@ class Test_note( object ): assert self.note.startup == self.startup assert self.note.deleted_from_id == None assert self.note.rank == self.rank + assert self.note.creation == self.creation def test_set_contents_with_html_title( self ): new_title = u"new title" @@ -55,6 +58,7 @@ class Test_note( object ): assert self.note.startup == self.startup assert self.note.deleted_from_id == None assert self.note.rank == self.rank + assert self.note.creation == self.creation def test_set_contents_with_multiple_titles( self ): new_title = u"new title" @@ -71,6 +75,7 @@ class Test_note( object ): assert self.note.startup == self.startup assert self.note.deleted_from_id == None assert self.note.rank == self.rank + assert self.note.creation == self.creation def test_set_notebook_id( self ): previous_revision = self.note.revision @@ -108,6 +113,7 @@ class Test_note( object ): assert d.get( "contents" ) == self.contents assert d.get( "title" ) == self.title assert d.get( "deleted_from_id" ) == None + assert d.get( "creation" ) == self.note.creation class Test_note_blank( Test_note ): @@ -118,6 +124,7 @@ class Test_note_blank( Test_note ): self.notebook_id = None self.startup = False self.rank = None + self.creation = None self.delta = timedelta( seconds = 1 ) self.note = Note.create( self.object_id ) @@ -131,3 +138,4 @@ class Test_note_blank( Test_note ): assert self.note.startup == False assert self.note.deleted_from_id == None assert self.note.rank == None + assert self.note.creation == None diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 96085c4..7b9b576 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -36,7 +36,7 @@ function Wiki( invoker ) { // populate the wiki with startup notes this.populate( evalJSON( getElement( "startup_notes" ).value || "null" ), - evalJSON( getElement( "note" ).value || "null" ), + evalJSON( getElement( "current_notes" ).value || "null" ), evalJSON( getElement( "note_read_write" ).value || "true" ) ); @@ -111,21 +111,22 @@ Wiki.prototype.display_storage_usage = function( storage_bytes ) { ); } -Wiki.prototype.populate = function ( startup_notes, note, note_read_write ) { +Wiki.prototype.populate = function ( startup_notes, current_notes, 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 ) { + // don't actually create an editor if a particular list of notes was provided in the result + if ( current_notes.length == 0 ) { var editor = this.create_editor( startup_note.object_id, - // grab this note's contents from the static