witten
/
luminotes
Archived
1
0
Fork 0

Some early work in support of a reverse chronological order view of a notebook, useful for a blog.

This commit is contained in:
Dan Helfman 2007-10-30 23:05:46 +00:00
parent 99c08bc3d1
commit ad58956f34
15 changed files with 359 additions and 80 deletions

View File

@ -74,6 +74,7 @@ def expose( view = None, rss = None ):
return unicode( view_override( **result ) ) return unicode( view_override( **result ) )
except: except:
if redirect is None: if redirect is None:
print result
raise raise
# if that doesn't work, and there's a redirect, then redirect # if that doesn't work, and there's a redirect, then redirect

View File

@ -68,6 +68,8 @@ class Notebooks( object ):
@param parent_id: id of parent notebook to this notebook (optional) @param parent_id: id of parent notebook to this notebook (optional)
@type revision: unicode or NoneType @type revision: unicode or NoneType
@param revision: revision timestamp of the provided note (optional) @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 @rtype: unicode
@return: rendered HTML page @return: rendered HTML page
""" """
@ -97,7 +99,7 @@ class Notebooks( object ):
'notebook': notebook, 'notebook': notebook,
'startup_notes': notelist, 'startup_notes': notelist,
'total_notes_count': notecount, '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 Access_error: the current user doesn't have access to the given notebook or note
@raise Validation_error: one of the arguments is invalid @raise Validation_error: one of the arguments is invalid
@ -130,7 +132,7 @@ class Notebooks( object ):
notebook = notebook, notebook = notebook,
startup_notes = startup_notes, startup_notes = startup_notes,
total_notes_count = total_notes_count, total_notes_count = total_notes_count,
note = note, notes = note and [ note ] or [],
) )
@expose( view = Json ) @expose( view = Json )
@ -693,3 +695,36 @@ class Notebooks( object ):
notebook_name = notebook.name, notebook_name = notebook.name,
notes = startup_notes + other_notes, 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

View File

@ -2,7 +2,7 @@ import cherrypy
from Expose import expose from Expose import expose
from Expire import strongly_expire from Expire import strongly_expire
from Validate import validate from Validate import validate, Valid_int
from Notebooks import Notebooks from Notebooks import Notebooks
from Users import Users, grab_user_id from Users import Users, grab_user_id
from Database import Valid_id from Database import Valid_id
@ -106,11 +106,36 @@ class Root( object ):
return dict( redirect = https_url ) return dict( redirect = https_url )
result = self.__users.current( user_id ) result = self.__users.current( user_id )
first_notebook_id = result[ u"notebooks" ][ 0 ].object_id main_notebooks = [ nb for nb in result[ "notebooks" ] if nb.name == u"Luminotes" ]
result.update( self.__notebooks.contents( first_notebook_id, user_id = user_id ) ) result.update( self.__notebooks.contents( main_notebooks[ 0 ].object_id, user_id = user_id ) )
return result 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 # TODO: move this method to controller.Notebooks, and maybe give it a more sensible name
@expose( view = Json ) @expose( view = Json )
def next_id( self ): def next_id( self ):

View File

@ -370,21 +370,22 @@ class Users( object ):
# in addition to this user's own notebooks, add to that list the anonymous user's notebooks # in addition to this user's own notebooks, add to that list the anonymous user's notebooks
login_url = None 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: 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 # if the user is not logged in, return a login URL
else: else:
if len( notebooks ) > 0 and notebooks[ 0 ]: notebooks = []
main_notebook = notebooks[ 0 ] 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" ) ) login_note = self.__database.select_one( Note, main_notebook.sql_load_note_by_title( u"login" ) )
if login_note: if login_note:
login_url = "%s/notebooks/%s?note_id=%s" % ( self.__https_url, main_notebook.object_id, login_note.object_id ) login_url = "%s/notebooks/%s?note_id=%s" % ( self.__https_url, main_notebook.object_id, login_note.object_id )
return dict( return dict(
user = user, user = user,
notebooks = notebooks, notebooks = notebooks + anon_notebooks,
login_url = login_url, login_url = login_url,
logout_url = self.__https_url + u"/", logout_url = self.__https_url + u"/",
rate_plan = ( user.rate_plan < len( self.__rate_plans ) ) and self.__rate_plans[ user.rate_plan ] or {}, 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[ "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[ "total_notes_count" ] = self.__database.select_one( Note, main_notebook.sql_count_notes() )
result[ "note_read_write" ] = False result[ "note_read_write" ] = False
result[ "note" ] = Note.create( result[ "notes" ] = [ Note.create(
object_id = u"password_reset", object_id = u"password_reset",
contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ), contents = unicode( Redeem_reset_note( password_reset_id, matching_users ) ),
notebook_id = main_notebook.object_id, notebook_id = main_notebook.object_id,
) ) ]
return result return result

View File

@ -134,6 +134,26 @@ class Valid_bool( object ):
raise ValueError() 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 ): def validate( **expected ):
""" """
validate() can be used to require that the arguments of the decorated method successfully pass validate() can be used to require that the arguments of the decorated method successfully pass

View File

@ -4,6 +4,7 @@ from Stub_view import Stub_view
from config import Common from config import Common
from datetime import datetime from datetime import datetime
from StringIO import StringIO from StringIO import StringIO
from copy import copy
class Test_controller( object ): class Test_controller( object ):
@ -128,6 +129,23 @@ class Test_controller( object ):
Notebook.sql_load_startup_notes = lambda self: \ Notebook.sql_load_startup_notes = lambda self: \
lambda database: sql_load_startup_notes( self, database ) 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 ): def sql_load_note_by_title( self, title, database ):
notes = [] notes = []

View File

@ -80,7 +80,7 @@ class Test_notebooks( Test_controller ):
assert result.get( u"notebook" ).object_id == self.notebook.object_id assert result.get( u"notebook" ).object_id == self.notebook.object_id
assert len( result.get( u"startup_notes" ) ) == 1 assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2 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"parent_id" ) == None
assert result.get( u"note_read_write" ) in ( None, True ) 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 result.get( u"notebook" ).object_id == self.notebook.object_id
assert len( result.get( u"startup_notes" ) ) == 1 assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2 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"parent_id" ) == None
assert result.get( u"note_read_write" ) in ( None, True ) 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 result.get( u"notebook" ).object_id == self.notebook.object_id
assert len( result.get( u"startup_notes" ) ) == 1 assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2 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"parent_id" ) == None
assert result.get( u"note_read_write" ) == False 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 result.get( u"notebook" ).object_id == self.notebook.object_id
assert len( result.get( u"startup_notes" ) ) == 1 assert len( result.get( u"startup_notes" ) ) == 1
assert result[ "total_notes_count" ] == 2 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"parent_id" ) == parent_id
assert result.get( u"note_read_write" ) in ( None, True ) assert result.get( u"note_read_write" ) in ( None, True )
@ -171,7 +177,7 @@ class Test_notebooks( Test_controller ):
notebook = result[ "notebook" ] notebook = result[ "notebook" ]
startup_notes = result[ "startup_notes" ] startup_notes = result[ "startup_notes" ]
assert result[ "total_notes_count" ] == 2 assert result[ "total_notes_count" ] == 2
assert result[ "note" ] == None assert result[ "notes" ] == []
assert notebook.object_id == self.notebook.object_id assert notebook.object_id == self.notebook.object_id
assert notebook.read_write == True assert notebook.read_write == True
@ -196,8 +202,11 @@ class Test_notebooks( Test_controller ):
assert len( startup_notes ) == 1 assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id 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.object_id == self.note.object_id
user = self.database.load( User, self.user.object_id ) user = self.database.load( User, self.user.object_id )
assert user.storage_bytes == 0 assert user.storage_bytes == 0
@ -220,8 +229,11 @@ class Test_notebooks( Test_controller ):
assert len( startup_notes ) == 1 assert len( startup_notes ) == 1
assert startup_notes[ 0 ].object_id == self.note.object_id 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.object_id == self.note.object_id
assert note.revision == self.note.revision assert note.revision == self.note.revision
user = self.database.load( User, self.user.object_id ) user = self.database.load( User, self.user.object_id )
@ -255,7 +267,7 @@ class Test_notebooks( Test_controller ):
notebook = result[ "notebook" ] notebook = result[ "notebook" ]
startup_notes = result[ "startup_notes" ] startup_notes = result[ "startup_notes" ]
assert result[ "note" ] == None assert result[ "notes" ] == []
assert result[ "total_notes_count" ] == 0 assert result[ "total_notes_count" ] == 0
assert notebook.object_id == self.anon_notebook.object_id assert notebook.object_id == self.anon_notebook.object_id
@ -1604,6 +1616,101 @@ class Test_notebooks( Test_controller ):
assert result.get( "error" ) 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 ): def login( self ):
result = self.http_post( "/users/login", dict( result = self.http_post( "/users/login", dict(
username = self.username, username = self.username,

View File

@ -13,7 +13,7 @@ class Test_root( Test_controller ):
self.notebook = Notebook.create( self.database.next_id( Notebook ), u"my notebook" ) self.notebook = Notebook.create( self.database.next_id( Notebook ), u"my notebook" )
self.database.save( self.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.database.save( self.anon_notebook )
self.anon_note = Note.create( self.anon_note = Note.create(
self.database.next_id( Note ), u"<h3>my note</h3>", self.database.next_id( Note ), u"<h3>my note</h3>",
@ -21,6 +21,14 @@ class Test_root( Test_controller ):
) )
self.database.save( self.anon_note ) 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"<h3>my blog entry</h3>",
notebook_id = self.blog_notebook.object_id,
)
self.database.save( self.blog_note )
self.username = u"mulder" self.username = u"mulder"
self.password = u"trustno1" self.password = u"trustno1"
self.email_address = u"outthere@example.com" 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.anonymous = User.create( self.database.next_id( User ), u"anonymous" )
self.database.save( self.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.anon_notebook.object_id ) )
self.database.execute( self.anonymous.sql_save_notebook( self.blog_notebook.object_id ) )
def test_index( self ): def test_index( self ):
result = self.http_get( "/" ) result = self.http_get( "/" )
@ -68,8 +77,9 @@ class Test_root( Test_controller ):
) )
assert result assert result
assert result[ u"note" ] assert result[ u"notes" ]
assert result[ u"note" ].object_id == self.anon_note.object_id 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 ): def test_default_with_unknown_note( self ):
result = self.http_get( result = self.http_get(
@ -99,6 +109,13 @@ class Test_root( Test_controller ):
assert result.get( "redirect" ) assert result.get( "redirect" )
assert result.get( "redirect" ).startswith( "https://" ) assert result.get( "redirect" ).startswith( "https://" )
def test_blog( self ):
result = self.http_get(
"/blog",
)
assert result
def test_next_id( self ): def test_next_id( self ):
result = self.http_get( "/next_id" ) result = self.http_get( "/next_id" )

View File

@ -104,26 +104,26 @@ class Test_users( Test_controller ):
notebooks = result[ u"notebooks" ] notebooks = result[ u"notebooks" ]
notebook = notebooks[ 0 ] 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.object_id == new_notebook_id
assert notebook.revision assert notebook.revision
assert notebook.name == u"my notebook" assert notebook.name == u"my notebook"
assert notebook.trash_id assert notebook.trash_id
assert notebook.read_write == True assert notebook.read_write == True
notebook = notebooks[ 2 ] notebook = notebooks[ 1 ]
assert notebook.object_id == notebooks[ 1 ].trash_id assert notebook.object_id == notebooks[ 0 ].trash_id
assert notebook.revision assert notebook.revision
assert notebook.name == u"trash" assert notebook.name == u"trash"
assert notebook.trash_id == None assert notebook.trash_id == None
assert notebook.read_write == True 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.get( u"login_url" ) is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" 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" ] notebooks = result[ u"notebooks" ]
assert len( notebooks ) == 3 assert len( notebooks ) == 3
notebook = notebooks[ 0 ] 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.object_id == new_notebook_id
assert notebook.revision assert notebook.revision
assert notebook.name == u"my notebook" assert notebook.name == u"my notebook"
assert notebook.trash_id assert notebook.trash_id
assert notebook.read_write == True assert notebook.read_write == True
notebook = notebooks[ 2 ] notebook = notebooks[ 1 ]
assert notebook.object_id == notebooks[ 1 ].trash_id assert notebook.object_id == notebooks[ 0 ].trash_id
assert notebook.revision assert notebook.revision
assert notebook.name == u"trash" assert notebook.name == u"trash"
assert notebook.trash_id == None assert notebook.trash_id == None
assert notebook.read_write == True 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.get( u"login_url" ) is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" 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" ].object_id == self.user.object_id
assert result[ u"user" ].username == self.user.username assert result[ u"user" ].username == self.user.username
assert len( result[ u"notebooks" ] ) == 3 assert len( result[ u"notebooks" ] ) == 3
assert result[ u"notebooks" ][ 0 ].object_id == self.anon_notebook.object_id assert result[ u"notebooks" ][ 0 ].object_id == self.notebooks[ 0 ].object_id
assert result[ u"notebooks" ][ 0 ].read_write == False assert result[ u"notebooks" ][ 0 ].read_write == True
assert result[ u"notebooks" ][ 1 ].object_id == self.notebooks[ 0 ].object_id assert result[ u"notebooks" ][ 1 ].object_id == self.notebooks[ 1 ].object_id
assert result[ u"notebooks" ][ 1 ].read_write == True 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 ].object_id == self.anon_notebook.object_id
assert result[ u"notebooks" ][ 2 ].read_write == True assert result[ u"notebooks" ][ 2 ].read_write == False
assert result[ u"login_url" ] is None assert result[ u"login_url" ] is None
assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/" 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 ].title == self.startup_note.title
assert result[ u"startup_notes" ][ 0 ].contents == self.startup_note.contents assert result[ u"startup_notes" ][ 0 ].contents == self.startup_note.contents
assert result[ u"note_read_write" ] is False 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 result[ u"notes" ]
assert u"password reset" in result[ u"note" ].contents assert len( result[ u"notes" ] ) == 1
assert self.user.username in result[ u"note" ].contents assert result[ u"notes" ][ 0 ].title == u"complete your password reset"
assert self.user2.username in result[ u"note" ].contents 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 ): def test_redeem_reset_unknown( self ):
password_reset_id = u"unknownresetid" password_reset_id = u"unknownresetid"

View File

@ -10,7 +10,7 @@ class Note( Persistent ):
TITLE_PATTERN = re.compile( u"<h3>(.*?)</h3>", flags = re.IGNORECASE ) TITLE_PATTERN = re.compile( u"<h3>(.*?)</h3>", flags = re.IGNORECASE )
def __init__( self, object_id, revision = None, title = None, contents = None, notebook_id = None, 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. 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) @param deleted_from_id: id of the notebook that this note was deleted from (optional)
@type rank: float or NoneType @type rank: float or NoneType
@param rank: indicates numeric ordering of this note in relation to other startup notes @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 @rtype: Note
@return: newly constructed note @return: newly constructed note
""" """
@ -40,9 +42,10 @@ class Note( Persistent ):
self.__startup = startup or False self.__startup = startup or False
self.__deleted_from_id = deleted_from_id self.__deleted_from_id = deleted_from_id
self.__rank = rank self.__rank = rank
self.__creation = creation
@staticmethod @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. 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) @param startup: whether this note should be displayed upon startup (optional, defaults to False)
@type rank: float or NoneType @type rank: float or NoneType
@param rank: indicates numeric ordering of this note in relation to other startup notes @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 @rtype: Note
@return: newly constructed 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 note.contents = contents
return note return note
@ -138,6 +143,7 @@ class Note( Persistent ):
contents = self.__contents, contents = self.__contents,
title = self.__title, title = self.__title,
deleted_from_id = self.__deleted_from_id, deleted_from_id = self.__deleted_from_id,
creation = self.__creation,
) ) ) )
return d return d
@ -148,3 +154,4 @@ class Note( Persistent ):
startup = property( lambda self: self.__startup, __set_startup ) startup = property( lambda self: self.__startup, __set_startup )
deleted_from_id = property( lambda self: self.__deleted_from_id, __set_deleted_from_id ) deleted_from_id = property( lambda self: self.__deleted_from_id, __set_deleted_from_id )
rank = property( lambda self: self.__rank, __set_rank ) rank = property( lambda self: self.__rank, __set_rank )
creation = property( lambda self: self.__creation )

View File

@ -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 ) 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 ): 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. Return a SQL string to load a particular note within this notebook by the note's id.

View File

@ -11,9 +11,10 @@ class Test_note( object ):
self.notebook_id = u"18" self.notebook_id = u"18"
self.startup = False self.startup = False
self.rank = 17.5 self.rank = 17.5
self.creation = datetime.now()
self.delta = timedelta( seconds = 1 ) 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 ): def test_create( self ):
assert self.note.object_id == self.object_id 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.startup == self.startup
assert self.note.deleted_from_id == None assert self.note.deleted_from_id == None
assert self.note.rank == self.rank assert self.note.rank == self.rank
assert self.note.creation == self.creation
def test_set_contents( self ): def test_set_contents( self ):
new_title = u"new title" new_title = u"new title"
@ -39,6 +41,7 @@ class Test_note( object ):
assert self.note.startup == self.startup assert self.note.startup == self.startup
assert self.note.deleted_from_id == None assert self.note.deleted_from_id == None
assert self.note.rank == self.rank assert self.note.rank == self.rank
assert self.note.creation == self.creation
def test_set_contents_with_html_title( self ): def test_set_contents_with_html_title( self ):
new_title = u"new title" new_title = u"new title"
@ -55,6 +58,7 @@ class Test_note( object ):
assert self.note.startup == self.startup assert self.note.startup == self.startup
assert self.note.deleted_from_id == None assert self.note.deleted_from_id == None
assert self.note.rank == self.rank assert self.note.rank == self.rank
assert self.note.creation == self.creation
def test_set_contents_with_multiple_titles( self ): def test_set_contents_with_multiple_titles( self ):
new_title = u"new title" new_title = u"new title"
@ -71,6 +75,7 @@ class Test_note( object ):
assert self.note.startup == self.startup assert self.note.startup == self.startup
assert self.note.deleted_from_id == None assert self.note.deleted_from_id == None
assert self.note.rank == self.rank assert self.note.rank == self.rank
assert self.note.creation == self.creation
def test_set_notebook_id( self ): def test_set_notebook_id( self ):
previous_revision = self.note.revision previous_revision = self.note.revision
@ -108,6 +113,7 @@ class Test_note( object ):
assert d.get( "contents" ) == self.contents assert d.get( "contents" ) == self.contents
assert d.get( "title" ) == self.title assert d.get( "title" ) == self.title
assert d.get( "deleted_from_id" ) == None assert d.get( "deleted_from_id" ) == None
assert d.get( "creation" ) == self.note.creation
class Test_note_blank( Test_note ): class Test_note_blank( Test_note ):
@ -118,6 +124,7 @@ class Test_note_blank( Test_note ):
self.notebook_id = None self.notebook_id = None
self.startup = False self.startup = False
self.rank = None self.rank = None
self.creation = None
self.delta = timedelta( seconds = 1 ) self.delta = timedelta( seconds = 1 )
self.note = Note.create( self.object_id ) 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.startup == False
assert self.note.deleted_from_id == None assert self.note.deleted_from_id == None
assert self.note.rank == None assert self.note.rank == None
assert self.note.creation == None

View File

@ -36,7 +36,7 @@ function Wiki( invoker ) {
// populate the wiki with startup notes // populate the wiki with startup notes
this.populate( this.populate(
evalJSON( getElement( "startup_notes" ).value || "null" ), evalJSON( getElement( "startup_notes" ).value || "null" ),
evalJSON( getElement( "note" ).value || "null" ), evalJSON( getElement( "current_notes" ).value || "null" ),
evalJSON( getElement( "note_read_write" ).value || "true" ) 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 // create an editor for each startup note in the received notebook, focusing the first one
var focus = true; var focus = true;
for ( var i in startup_notes ) { for ( var i in startup_notes ) {
var startup_note = startup_notes[ i ]; var startup_note = startup_notes[ i ];
this.startup_notes[ startup_note.object_id ] = true; this.startup_notes[ startup_note.object_id ] = true;
// don't actually create an editor if a particular note was provided in the result // don't actually create an editor if a particular list of notes was provided in the result
if ( !note ) { if ( current_notes.length == 0 ) {
var editor = this.create_editor( var editor = this.create_editor(
startup_note.object_id, startup_note.object_id,
// grab this note's contents from the static <noscript> area // grab this note's contents from the static notes area
getElement( "static_note_" + startup_note.object_id ).innerHTML, getElement( "static_note_" + startup_note.object_id ).innerHTML,
startup_note.deleted_from_id, startup_note.deleted_from_id,
startup_note.revision, startup_note.revision,
startup_note.creation,
this.notebook.read_write, false, focus this.notebook.read_write, false, focus
); );
@ -134,17 +135,23 @@ Wiki.prototype.populate = function ( startup_notes, note, note_read_write ) {
} }
} }
// if one particular note was provided, then just display an editor for that note // if particular notes were provided, then display editors for them
if ( note ) var focus = true;
for ( var i in current_notes ) {
var note = current_notes[ i ];
this.create_editor( this.create_editor(
note.object_id, note.object_id,
getElement( "static_note_" + note.object_id ).innerHTML, getElement( "static_note_" + note.object_id ).innerHTML,
note.deleted_from_id, note.deleted_from_id,
note.revision, note.revision,
this.notebook.read_write && note_read_write, false, true note.creation,
this.notebook.read_write && note_read_write, false, focus
); );
focus = false;
}
if ( startup_notes.length == 0 && !note ) if ( startup_notes.length == 0 && current_notes.length == 0 )
this.display_empty_message(); this.display_empty_message();
var self = this; var self = this;
@ -222,7 +229,7 @@ Wiki.prototype.create_blank_editor = function ( event ) {
} }
} }
var editor = this.create_editor( 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 );
this.increment_total_notes_count(); this.increment_total_notes_count();
this.blank_editor_id = editor.id; this.blank_editor_id = editor.id;
@ -416,6 +423,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
if ( requested_revision ) if ( requested_revision )
id += " " + requested_revision; id += " " + requested_revision;
var actual_revision = result.note.revision; var actual_revision = result.note.revision;
var actual_creation = result.note.creation;
var note_text = result.note.contents; var note_text = result.note.contents;
var deleted_from_id = result.note.deleted; var deleted_from_id = result.note.deleted;
} else { } else {
@ -423,6 +431,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
var note_text = "<h3>" + note_title; var note_text = "<h3>" + note_title;
var deleted_from_id = null; var deleted_from_id = null;
var actual_revision = null; var actual_revision = null;
var actual_creation = null;
this.increment_total_notes_count(); this.increment_total_notes_count();
} }
@ -431,7 +440,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
else else
var read_write = this.notebook.read_write; var read_write = this.notebook.read_write;
var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, read_write, true, false ); var editor = this.create_editor( id, note_text, deleted_from_id, actual_revision, actual_creation, read_write, true, false );
id = editor.id; id = editor.id;
// if a link that launched this editor was provided, update it with the created note's id // if a link that launched this editor was provided, update it with the created note's id
@ -439,7 +448,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
link.href = "/notebooks/" + this.notebook_id + "?note_id=" + id; link.href = "/notebooks/" + this.notebook_id + "?note_id=" + id;
} }
Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, read_write, highlight, focus ) { Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revision, creation, read_write, highlight, focus ) {
var self = this; var self = this;
if ( isUndefinedOrNull( id ) ) { if ( isUndefinedOrNull( id ) ) {
if ( this.notebook.read_write ) { if ( this.notebook.read_write ) {
@ -458,6 +467,11 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi
note_text = "<p>Previous revision from " + short_revision + "</p>" + note_text; note_text = "<p>Previous revision from " + short_revision + "</p>" + note_text;
} }
if ( !read_write && creation ) {
var short_creation = this.brief_revision( creation );
note_text = "<p>" + short_creation + "</p>" + note_text;
}
var startup = this.startup_notes[ id ]; var startup = this.startup_notes[ id ];
var editor = new Editor( id, this.notebook_id, note_text, deleted_from_id, revision, read_write, startup, highlight, focus ); var editor = new Editor( id, this.notebook_id, note_text, deleted_from_id, revision, read_write, startup, highlight, focus );

View File

@ -3,6 +3,8 @@ from Tags import Div, Span, H4, A
class Link_area( Div ): class Link_area( Div ):
def __init__( self, notebooks, notebook, total_notes_count, parent_id ): def __init__( self, notebooks, notebook, total_notes_count, parent_id ):
linked_notebooks = [ nb for nb in notebooks if nb.read_write and nb.name not in ( u"trash" ) ]
Div.__init__( Div.__init__(
self, self,
Div( Div(
@ -57,7 +59,7 @@ class Link_area( Div ):
), ),
Div( Div(
( len( notebooks ) > 1 ) and H4( u"notebooks" ) or None, ( len( linked_notebooks ) > 0 ) and H4( u"notebooks" ) or None,
[ Span( [ Span(
Div( Div(
A( A(
@ -66,7 +68,7 @@ class Link_area( Div ):
id = u"notebook_%s" % nb.object_id, id = u"notebook_%s" % nb.object_id,
), ),
class_ = ( nb.object_id == notebook.object_id ) and u"link_area_item current_notebook_name" or u"link_area_item", class_ = ( nb.object_id == notebook.object_id ) and u"link_area_item current_notebook_name" or u"link_area_item",
) ) for nb in notebooks if nb.name not in ( u"Luminotes", u"trash" ) ], ) ) for nb in linked_notebooks ],
id = u"notebooks_area" id = u"notebooks_area"
), ),

View File

@ -20,16 +20,16 @@ class Main_page( Page ):
logout_url = None, logout_url = None,
startup_notes = None, startup_notes = None,
total_notes_count = None, total_notes_count = None,
note = None, notes = None,
note_read_write = True, note_read_write = True,
): ):
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ] startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
static_notes = Div( static_notes = Div(
note and Div( notes and [ Div(
note.contents, note.contents,
id = "static_note_%s" % note.object_id, id = "static_note_%s" % note.object_id,
) or ) for note in notes ] or
[ Div( [ Div(
startup_note.contents, startup_note.contents,
id = "static_note_%s" % startup_note.object_id id = "static_note_%s" % startup_note.object_id
@ -46,12 +46,12 @@ class Main_page( Page ):
u"deleted_from_id" : startup_note.deleted_from_id, u"deleted_from_id" : startup_note.deleted_from_id,
} for startup_note in startup_notes ] } for startup_note in startup_notes ]
if note: note_dicts = [ {
note_dict = { u"object_id" : note.object_id,
u"object_id" : note.object_id, u"revision" : note.revision,
u"revision" : note.revision, u"deleted_from_id" : note.deleted_from_id,
u"deleted_from_id" : note.deleted_from_id, u"creation" : note.creation,
} } for note in notes ]
def json( string ): def json( string ):
return escape( unicode( Json( string ) ), quote = True ) return escape( unicode( Json( string ) ), quote = True )
@ -66,7 +66,7 @@ class Main_page( Page ):
Input( type = u"hidden", name = u"notebook_id", id = u"notebook_id", value = notebook.object_id ), Input( type = u"hidden", name = u"notebook_id", id = u"notebook_id", value = notebook.object_id ),
Input( type = u"hidden", name = u"parent_id", id = u"parent_id", value = parent_id or "" ), Input( type = u"hidden", name = u"parent_id", id = u"parent_id", value = parent_id or "" ),
Input( type = u"hidden", name = u"startup_notes", id = u"startup_notes", value = json( startup_note_dicts ) ), Input( type = u"hidden", name = u"startup_notes", id = u"startup_notes", value = json( startup_note_dicts ) ),
Input( type = u"hidden", name = u"note", id = u"note", value = note and json( note_dict ) or "" ), Input( type = u"hidden", name = u"current_notes", id = u"current_notes", value = json( note_dicts ) ),
Input( type = u"hidden", name = u"note_read_write", id = u"note_read_write", value = json( note_read_write ) ), Input( type = u"hidden", name = u"note_read_write", id = u"note_read_write", value = json( note_read_write ) ),
Div( Div(
id = u"status_area", id = u"status_area",