Some early work in support of a reverse chronological order view of a notebook, useful for a blog.
This commit is contained in:
parent
99c08bc3d1
commit
ad58956f34
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = []
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"<h3>my note</h3>",
|
||||
|
@ -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"<h3>my blog entry</h3>",
|
||||
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" )
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -10,7 +10,7 @@ class Note( Persistent ):
|
|||
TITLE_PATTERN = re.compile( u"<h3>(.*?)</h3>", 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 )
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <noscript> area
|
||||
// grab this note's contents from the static notes area
|
||||
getElement( "static_note_" + startup_note.object_id ).innerHTML,
|
||||
startup_note.deleted_from_id,
|
||||
startup_note.revision,
|
||||
startup_note.creation,
|
||||
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 ( note )
|
||||
// if particular notes were provided, then display editors for them
|
||||
var focus = true;
|
||||
for ( var i in current_notes ) {
|
||||
var note = current_notes[ i ];
|
||||
|
||||
this.create_editor(
|
||||
note.object_id,
|
||||
getElement( "static_note_" + note.object_id ).innerHTML,
|
||||
note.deleted_from_id,
|
||||
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();
|
||||
|
||||
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.blank_editor_id = editor.id;
|
||||
|
||||
|
@ -416,6 +423,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
|
|||
if ( requested_revision )
|
||||
id += " " + requested_revision;
|
||||
var actual_revision = result.note.revision;
|
||||
var actual_creation = result.note.creation;
|
||||
var note_text = result.note.contents;
|
||||
var deleted_from_id = result.note.deleted;
|
||||
} else {
|
||||
|
@ -423,6 +431,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
|
|||
var note_text = "<h3>" + note_title;
|
||||
var deleted_from_id = null;
|
||||
var actual_revision = null;
|
||||
var actual_creation = null;
|
||||
this.increment_total_notes_count();
|
||||
}
|
||||
|
||||
|
@ -431,7 +440,7 @@ Wiki.prototype.parse_loaded_editor = function ( result, note_title, requested_re
|
|||
else
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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;
|
||||
if ( isUndefinedOrNull( id ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 editor = new Editor( id, this.notebook_id, note_text, deleted_from_id, revision, read_write, startup, highlight, focus );
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ from Tags import Div, Span, H4, A
|
|||
|
||||
class Link_area( Div ):
|
||||
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__(
|
||||
self,
|
||||
Div(
|
||||
|
@ -57,7 +59,7 @@ class Link_area( Div ):
|
|||
),
|
||||
|
||||
Div(
|
||||
( len( notebooks ) > 1 ) and H4( u"notebooks" ) or None,
|
||||
( len( linked_notebooks ) > 0 ) and H4( u"notebooks" ) or None,
|
||||
[ Span(
|
||||
Div(
|
||||
A(
|
||||
|
@ -66,7 +68,7 @@ class Link_area( Div ):
|
|||
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",
|
||||
) ) for nb in notebooks if nb.name not in ( u"Luminotes", u"trash" ) ],
|
||||
) ) for nb in linked_notebooks ],
|
||||
id = u"notebooks_area"
|
||||
),
|
||||
|
||||
|
|
|
@ -20,16 +20,16 @@ class Main_page( Page ):
|
|||
logout_url = None,
|
||||
startup_notes = None,
|
||||
total_notes_count = None,
|
||||
note = None,
|
||||
notes = None,
|
||||
note_read_write = True,
|
||||
):
|
||||
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
||||
|
||||
static_notes = Div(
|
||||
note and Div(
|
||||
notes and [ Div(
|
||||
note.contents,
|
||||
id = "static_note_%s" % note.object_id,
|
||||
) or
|
||||
) for note in notes ] or
|
||||
[ Div(
|
||||
startup_note.contents,
|
||||
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,
|
||||
} for startup_note in startup_notes ]
|
||||
|
||||
if note:
|
||||
note_dict = {
|
||||
u"object_id" : note.object_id,
|
||||
u"revision" : note.revision,
|
||||
u"deleted_from_id" : note.deleted_from_id,
|
||||
}
|
||||
note_dicts = [ {
|
||||
u"object_id" : note.object_id,
|
||||
u"revision" : note.revision,
|
||||
u"deleted_from_id" : note.deleted_from_id,
|
||||
u"creation" : note.creation,
|
||||
} for note in notes ]
|
||||
|
||||
def json( string ):
|
||||
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"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"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 ) ),
|
||||
Div(
|
||||
id = u"status_area",
|
||||
|
|
Reference in New Issue