Began work on notebook deletion and subsequent undo/undeletion.
Changed schema slightly to support this. Added a schema delta file and wrote an UPGRADE doc with info on how to upgrade schemas.
This commit is contained in:
parent
c67aba8fbc
commit
cdd971780e
|
@ -0,0 +1,12 @@
|
|||
To upgrade the Luminotes database from an earlier version, manually apply each
|
||||
relevant schema delta file within model/delta/
|
||||
|
||||
For instance, if you were upgrading from version 1.0.1 to 1.0.4, you would
|
||||
apply the following deltas in order:
|
||||
|
||||
psql -U luminotes luminotes -f model/delta/1.0.2.sql
|
||||
psql -U luminotes luminotes -f model/delta/1.0.3.sql
|
||||
psql -U luminotes luminotes -f model/delta/1.0.4.sql
|
||||
|
||||
Any version which does not introduce a schema change does not have a
|
||||
corresponding schema delta file.
|
|
@ -54,9 +54,11 @@ class Notebooks( object ):
|
|||
parent_id = Valid_id(),
|
||||
revision = Valid_revision(),
|
||||
rename = Valid_bool(),
|
||||
deleted_id = Valid_id(),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def default( self, notebook_id, note_id = None, parent_id = None, revision = None, rename = False, user_id = None ):
|
||||
def default( self, notebook_id, note_id = None, parent_id = None, revision = None, rename = False,
|
||||
deleted_id = None, user_id = None ):
|
||||
"""
|
||||
Provide the information necessary to display the page for a particular notebook. If a
|
||||
particular note id is given without a revision, then the most recent version of that note is
|
||||
|
@ -70,6 +72,10 @@ 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 rename: bool or NoneType
|
||||
@param rename: whether this is a new notebook and should be renamed (optional, defaults to False)
|
||||
@type deleted_id: unicode or NoneType
|
||||
@param deleted_id: id of the notebook that was just deleted, if any (optional)
|
||||
@type user_id: unicode or NoneType
|
||||
@param user_id: id of current logged-in user (if any)
|
||||
@rtype: unicode
|
||||
|
@ -89,6 +95,7 @@ class Notebooks( object ):
|
|||
else:
|
||||
result[ "conversion" ] = u"signup"
|
||||
result[ "rename" ] = rename
|
||||
result[ "deleted_id" ] = deleted_id
|
||||
|
||||
return result
|
||||
|
||||
|
@ -725,24 +732,30 @@ class Notebooks( object ):
|
|||
raise Access_error()
|
||||
|
||||
user = self.__database.load( User, user_id )
|
||||
notebook = self.__create_notebook( u"new notebook", user )
|
||||
|
||||
return dict(
|
||||
redirect = u"/notebooks/%s?rename=true" % notebook.object_id,
|
||||
)
|
||||
|
||||
def __create_notebook( self, name, user, commit = True ):
|
||||
# create the notebook along with a trash
|
||||
trash_id = self.__database.next_id( Notebook, commit = False )
|
||||
trash = Notebook.create( trash_id, u"trash" )
|
||||
self.__database.save( trash, commit = False )
|
||||
|
||||
notebook_id = self.__database.next_id( Notebook, commit = False )
|
||||
notebook = Notebook.create( notebook_id, u"new notebook", trash_id )
|
||||
notebook = Notebook.create( notebook_id, name, trash_id )
|
||||
self.__database.save( notebook, commit = False )
|
||||
|
||||
# record the fact that the user has access to their new notebook
|
||||
self.__database.execute( user.sql_save_notebook( notebook_id, read_write = True ), commit = False )
|
||||
self.__database.execute( user.sql_save_notebook( trash_id, read_write = True ), commit = False )
|
||||
self.__database.commit()
|
||||
|
||||
return dict(
|
||||
redirect = u"/notebooks/%s?rename=true" % notebook_id,
|
||||
)
|
||||
if commit:
|
||||
self.__database.commit()
|
||||
|
||||
return notebook
|
||||
|
||||
@expose( view = Json )
|
||||
@grab_user_id
|
||||
|
@ -766,6 +779,7 @@ class Notebooks( object ):
|
|||
@raise Access_error: the current user doesn't have access to the given notebook
|
||||
@raise Validation_error: one of the arguments is invalid
|
||||
"""
|
||||
user = self.__database.load( User, user_id )
|
||||
if not self.__users.check_access( user_id, notebook_id, read_write = True ):
|
||||
raise Access_error()
|
||||
|
||||
|
@ -792,6 +806,95 @@ class Notebooks( object ):
|
|||
|
||||
return dict()
|
||||
|
||||
@expose( view = Json )
|
||||
@grab_user_id
|
||||
@validate(
|
||||
notebook_id = Valid_id(),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def delete( self, notebook_id, user_id ):
|
||||
"""
|
||||
Delete the given notebook and redirect to a remaining notebook. If there is none, create one.
|
||||
|
||||
@type notebook_id: unicode
|
||||
@param notebook_id: id of notebook to delete
|
||||
@type user_id: unicode or NoneType
|
||||
@param user_id: id of current logged-in user (if any)
|
||||
@rtype dict
|
||||
@return { "redirect": remainingnotebookurl }
|
||||
@raise Access_error: the current user doesn't have access to the given notebook
|
||||
@raise Validation_error: one of the arguments is invalid
|
||||
"""
|
||||
if user_id is None:
|
||||
raise Access_error()
|
||||
|
||||
user = self.__database.load( User, user_id )
|
||||
|
||||
if not self.__users.check_access( user_id, notebook_id, read_write = True ):
|
||||
raise Access_error()
|
||||
|
||||
notebook = self.__database.load( Notebook, notebook_id )
|
||||
|
||||
# TODO: maybe if notebook.deleted is already True, then the notebook should be "deleted forever"
|
||||
if not notebook:
|
||||
raise Access_error()
|
||||
|
||||
# prevent deletion of a trash notebook directly
|
||||
if notebook.name == u"trash":
|
||||
raise Access_error()
|
||||
|
||||
notebook.deleted = True
|
||||
self.__database.save( notebook, commit = False )
|
||||
|
||||
# redirect to a remaining undeleted notebook, or if there isn't one, create an empty notebook
|
||||
remaining_notebook = self.__database.select_one( Notebook, user.sql_load_notebooks( parents_only = True ) )
|
||||
if remaining_notebook is None:
|
||||
remaining_notebook = self.__create_notebook( u"my notebook", user, commit = False )
|
||||
|
||||
self.__database.commit()
|
||||
|
||||
return dict(
|
||||
redirect = u"/notebooks/%s?deleted_id=%s" % ( remaining_notebook.object_id, notebook.object_id ),
|
||||
)
|
||||
|
||||
@expose( view = Json )
|
||||
@grab_user_id
|
||||
@validate(
|
||||
notebook_id = Valid_id(),
|
||||
user_id = Valid_id( none_okay = True ),
|
||||
)
|
||||
def undelete( self, notebook_id, user_id ):
|
||||
"""
|
||||
Undelete the given notebook and redirect to it.
|
||||
|
||||
@type notebook_id: unicode
|
||||
@param notebook_id: id of notebook to undelete
|
||||
@type user_id: unicode or NoneType
|
||||
@param user_id: id of current logged-in user (if any)
|
||||
@rtype dict
|
||||
@return { "redirect": notebookurl }
|
||||
@raise Access_error: the current user doesn't have access to the given notebook
|
||||
@raise Validation_error: one of the arguments is invalid
|
||||
"""
|
||||
if user_id is None:
|
||||
raise Access_error()
|
||||
|
||||
if not self.__users.check_access( user_id, notebook_id, read_write = True ):
|
||||
raise Access_error()
|
||||
|
||||
notebook = self.__database.load( Notebook, notebook_id )
|
||||
|
||||
if not notebook:
|
||||
raise Access_error()
|
||||
|
||||
notebook.deleted = False
|
||||
self.__database.save( notebook, commit = False )
|
||||
self.__database.commit()
|
||||
|
||||
return dict(
|
||||
redirect = u"/notebooks/%s" % notebook.object_id,
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -26,7 +26,7 @@ class Test_controller( object ):
|
|||
User.sql_save_notebook = lambda self, notebook_id, read_write = False: \
|
||||
lambda database: sql_save_notebook( self, notebook_id, read_write, database )
|
||||
|
||||
def sql_load_notebooks( self, parents_only, database ):
|
||||
def sql_load_notebooks( self, parents_only, deleted, database ):
|
||||
notebooks = []
|
||||
notebook_tuples = database.user_notebook.get( self.object_id )
|
||||
|
||||
|
@ -38,12 +38,14 @@ class Test_controller( object ):
|
|||
notebook._Notebook__read_write = read_write
|
||||
if parents_only and notebook.trash_id is None:
|
||||
continue
|
||||
if deleted != notebook.deleted:
|
||||
continue
|
||||
notebooks.append( notebook )
|
||||
|
||||
return notebooks
|
||||
|
||||
User.sql_load_notebooks = lambda self, parents_only = False: \
|
||||
lambda database: sql_load_notebooks( self, parents_only, database )
|
||||
User.sql_load_notebooks = lambda self, parents_only = False, deleted = False: \
|
||||
lambda database: sql_load_notebooks( self, parents_only, deleted, database )
|
||||
|
||||
def sql_load_by_username( username, database ):
|
||||
users = []
|
||||
|
|
|
@ -1760,6 +1760,159 @@ class Test_notebooks( Test_controller ):
|
|||
|
||||
assert result[ u"error" ]
|
||||
|
||||
def test_delete( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ u"redirect" ].startswith( u"/notebooks/" )
|
||||
|
||||
# assert that we're redirected to a newly created notebook
|
||||
remaining_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ].split( u"?" )[ 0 ]
|
||||
notebook = self.database.last_saved_obj
|
||||
|
||||
assert isinstance( notebook, Notebook )
|
||||
assert notebook.object_id == remaining_notebook_id
|
||||
assert notebook.name == u"my notebook"
|
||||
assert notebook.read_write == True
|
||||
assert notebook.trash_id
|
||||
|
||||
def test_delete_with_multiple_notebooks( self ):
|
||||
# create a second notebook, which we should be redirected to after the first notebook is deleted
|
||||
trash = Notebook.create( self.database.next_id( Notebook ), u"trash" )
|
||||
self.database.save( trash, commit = False )
|
||||
notebook = Notebook.create( self.database.next_id( Notebook ), u"notebook", trash.object_id )
|
||||
self.database.save( notebook, commit = False )
|
||||
self.database.execute( self.user.sql_save_notebook( notebook.object_id, read_write = True ) )
|
||||
self.database.execute( self.user.sql_save_notebook( notebook.trash_id, read_write = True ) )
|
||||
self.database.commit()
|
||||
|
||||
self.login()
|
||||
|
||||
result = self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ u"redirect" ].startswith( u"/notebooks/" )
|
||||
|
||||
# assert that we're redirected to the second notebook
|
||||
remaining_notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ].split( u"?" )[ 0 ]
|
||||
assert remaining_notebook_id
|
||||
assert remaining_notebook_id == notebook.object_id
|
||||
|
||||
def test_contents_after_delete( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.notebook.object_id,
|
||||
user_id = self.user.object_id,
|
||||
)
|
||||
|
||||
notebook = result[ "notebook" ]
|
||||
assert notebook.deleted == True
|
||||
|
||||
def test_delete_without_login( self ):
|
||||
result = self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ u"error" ]
|
||||
|
||||
def test_delete_trash( self ):
|
||||
self.login()
|
||||
|
||||
result = self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.trash_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert u"error" in result
|
||||
|
||||
def test_undelete( self ):
|
||||
self.login()
|
||||
|
||||
self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
result = self.http_post( "/notebooks/undelete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ u"redirect" ].startswith( u"/notebooks/" )
|
||||
|
||||
# assert that we're redirected to the undeleted notebook
|
||||
notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||
notebook = self.database.last_saved_obj
|
||||
|
||||
assert isinstance( notebook, Notebook )
|
||||
assert notebook.object_id == notebook_id
|
||||
assert notebook.name == self.notebook.name
|
||||
assert notebook.read_write == True
|
||||
assert notebook.trash_id
|
||||
|
||||
def test_contents_after_undelete( self ):
|
||||
self.login()
|
||||
|
||||
self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
result = self.http_post( "/notebooks/undelete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
result = cherrypy.root.notebooks.contents(
|
||||
notebook_id = self.notebook.object_id,
|
||||
user_id = self.user.object_id,
|
||||
)
|
||||
|
||||
notebook = result[ "notebook" ]
|
||||
assert notebook.deleted == False
|
||||
|
||||
def test_undelete_without_login( self ):
|
||||
self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
result = self.http_post( "/notebooks/undelete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ u"error" ]
|
||||
|
||||
def test_undelete_twice( self ):
|
||||
self.login()
|
||||
|
||||
self.http_post( "/notebooks/delete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
self.http_post( "/notebooks/undelete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
result = self.http_post( "/notebooks/undelete", dict(
|
||||
notebook_id = self.notebook.object_id,
|
||||
), session_id = self.session_id )
|
||||
|
||||
assert result[ u"redirect" ].startswith( u"/notebooks/" )
|
||||
|
||||
# assert that we're redirected to the undeleted notebook
|
||||
notebook_id = result[ u"redirect" ].split( u"/notebooks/" )[ -1 ]
|
||||
notebook = self.database.last_saved_obj
|
||||
|
||||
assert isinstance( notebook, Notebook )
|
||||
assert notebook.object_id == notebook_id
|
||||
assert notebook.name == self.notebook.name
|
||||
assert notebook.read_write == True
|
||||
assert notebook.trash_id
|
||||
|
||||
def test_recent_notes( self ):
|
||||
result = cherrypy.root.notebooks.load_recent_notes(
|
||||
self.notebook.object_id,
|
||||
|
|
|
@ -12,7 +12,7 @@ class Notebook( Persistent ):
|
|||
WHITESPACE_PATTERN = re.compile( r"\s+" )
|
||||
SEARCH_OPERATORS = re.compile( r"[&|!()]" )
|
||||
|
||||
def __init__( self, object_id, revision = None, name = None, trash_id = None, read_write = True ):
|
||||
def __init__( self, object_id, revision = None, name = None, trash_id = None, deleted = False, read_write = True ):
|
||||
"""
|
||||
Create a new notebook with the given id and name.
|
||||
|
||||
|
@ -24,6 +24,8 @@ class Notebook( Persistent ):
|
|||
@param name: name of this notebook (optional)
|
||||
@type trash_id: Notebook or NoneType
|
||||
@param trash_id: id of the notebook where deleted notes from this notebook go to die (optional)
|
||||
@type deleted: bool or NoneType
|
||||
@param deleted: whether this notebook is currently deleted (optional, defaults to False)
|
||||
@type read_write: bool or NoneType
|
||||
@param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
|
||||
@rtype: Notebook
|
||||
|
@ -32,10 +34,11 @@ class Notebook( Persistent ):
|
|||
Persistent.__init__( self, object_id, revision )
|
||||
self.__name = name
|
||||
self.__trash_id = trash_id
|
||||
self.__deleted = deleted
|
||||
self.__read_write = read_write
|
||||
|
||||
@staticmethod
|
||||
def create( object_id, name = None, trash_id = None, read_write = True ):
|
||||
def create( object_id, name = None, trash_id = None, deleted = False, read_write = True ):
|
||||
"""
|
||||
Convenience constructor for creating a new notebook.
|
||||
|
||||
|
@ -45,6 +48,8 @@ class Notebook( Persistent ):
|
|||
@param name: name of this notebook (optional)
|
||||
@type trash_id: Notebook or NoneType
|
||||
@param trash_id: id of the notebook where deleted notes from this notebook go to die (optional)
|
||||
@type deleted: bool or NoneType
|
||||
@param deleted: whether this notebook is currently deleted (optional, defaults to False)
|
||||
@type read_write: bool or NoneType
|
||||
@param read_write: whether this view of the notebook is currently read-write (optional, defaults to True)
|
||||
@rtype: Notebook
|
||||
|
@ -71,10 +76,10 @@ class Notebook( Persistent ):
|
|||
|
||||
def sql_create( self ):
|
||||
return \
|
||||
"insert into notebook ( id, revision, name, trash_id ) " + \
|
||||
"values ( %s, %s, %s, %s );" % \
|
||||
"insert into notebook ( id, revision, name, trash_id, deleted ) " + \
|
||||
"values ( %s, %s, %s, %s, %s );" % \
|
||||
( quote( self.object_id ), quote( self.revision ), quote( self.__name ),
|
||||
quote( self.__trash_id ) )
|
||||
quote( self.__trash_id ), quote( self.deleted ) )
|
||||
|
||||
def sql_update( self ):
|
||||
return self.sql_create()
|
||||
|
@ -187,6 +192,7 @@ class Notebook( Persistent ):
|
|||
name = self.__name,
|
||||
trash_id = self.__trash_id,
|
||||
read_write = self.__read_write,
|
||||
deleted = self.__deleted,
|
||||
) )
|
||||
|
||||
return d
|
||||
|
@ -196,8 +202,15 @@ class Notebook( Persistent ):
|
|||
self.update_revision()
|
||||
|
||||
def __set_read_write( self, read_write ):
|
||||
# The read_write member isn't actually saved to the database, so setting it doesn't need to
|
||||
# call update_revision().
|
||||
self.__read_write = read_write
|
||||
|
||||
def __set_deleted( self, deleted ):
|
||||
self.__deleted = deleted
|
||||
self.update_revision()
|
||||
|
||||
name = property( lambda self: self.__name, __set_name )
|
||||
trash_id = property( lambda self: self.__trash_id )
|
||||
read_write = property( lambda self: self.__read_write, __set_read_write )
|
||||
deleted = property( lambda self: self.__deleted, __set_deleted )
|
||||
|
|
|
@ -129,19 +129,24 @@ class User( Persistent ):
|
|||
def sql_load_by_email_address( email_address ):
|
||||
return "select * from luminotes_user_current where email_address = %s;" % quote( email_address )
|
||||
|
||||
def sql_load_notebooks( self, parents_only = False ):
|
||||
def sql_load_notebooks( self, parents_only = False, deleted = False ):
|
||||
"""
|
||||
Return a SQL string to load a list of the notebooks to which this user has access.
|
||||
"""
|
||||
if parents_only:
|
||||
parents_only_clause = " and trash_id is not null";
|
||||
parents_only_clause = " and trash_id is not null"
|
||||
else:
|
||||
parents_only_clause = ""
|
||||
|
||||
if deleted:
|
||||
deleted_clause = " and deleted = 't'"
|
||||
else:
|
||||
deleted_clause = " and deleted = 'f'"
|
||||
|
||||
return \
|
||||
"select notebook_current.*, user_notebook.read_write from user_notebook, notebook_current " + \
|
||||
"where user_id = %s%s and user_notebook.notebook_id = notebook_current.id order by revision;" % \
|
||||
( quote( self.object_id ), parents_only_clause )
|
||||
"where user_id = %s%s%s and user_notebook.notebook_id = notebook_current.id order by revision;" % \
|
||||
( quote( self.object_id ), parents_only_clause, deleted_clause )
|
||||
|
||||
def sql_save_notebook( self, notebook_id, read_write = True ):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
alter table notebook add column deleted boolean default 'f';
|
||||
drop view notebook_current;
|
||||
create view notebook_current as select notebook.id, notebook.revision, notebook.name, notebook.trash_id, notebook.deleted from notebook where (notebook.revision in (select max(sub_notebook.revision) as max from notebook sub_notebook where (sub_notebook.id = notebook.id)));
|
|
@ -96,7 +96,8 @@ CREATE TABLE notebook (
|
|||
id text NOT NULL,
|
||||
revision timestamp with time zone NOT NULL,
|
||||
name text,
|
||||
trash_id text
|
||||
trash_id text,
|
||||
deleted boolean DEFAULT false
|
||||
);
|
||||
|
||||
|
||||
|
@ -107,7 +108,7 @@ ALTER TABLE public.notebook OWNER TO luminotes;
|
|||
--
|
||||
|
||||
CREATE VIEW notebook_current AS
|
||||
SELECT notebook.id, notebook.revision, notebook.name, notebook.trash_id FROM notebook WHERE (notebook.revision IN (SELECT max(sub_notebook.revision) AS max FROM notebook sub_notebook WHERE (sub_notebook.id = notebook.id)));
|
||||
SELECT notebook.id, notebook.revision, notebook.name, notebook.trash_id, notebook.deleted FROM notebook WHERE (notebook.revision IN (SELECT max(sub_notebook.revision) AS max FROM notebook sub_notebook WHERE (sub_notebook.id = notebook.id)));
|
||||
|
||||
|
||||
ALTER TABLE public.notebook_current OWNER TO luminotes;
|
||||
|
|
|
@ -12,8 +12,8 @@ class Test_notebook( object ):
|
|||
self.trash_name = u"trash"
|
||||
self.delta = timedelta( seconds = 1 )
|
||||
|
||||
self.trash = Notebook.create( self.trash_id, self.trash_name, read_write = False )
|
||||
self.notebook = Notebook.create( self.object_id, self.name, trash_id = self.trash.object_id )
|
||||
self.trash = Notebook.create( self.trash_id, self.trash_name, read_write = False, deleted = False )
|
||||
self.notebook = Notebook.create( self.object_id, self.name, trash_id = self.trash.object_id, deleted = False )
|
||||
self.note = Note.create( "19", u"<h3>title</h3>blah" )
|
||||
|
||||
def test_create( self ):
|
||||
|
@ -22,12 +22,14 @@ class Test_notebook( object ):
|
|||
assert self.notebook.name == self.name
|
||||
assert self.notebook.read_write == True
|
||||
assert self.notebook.trash_id == self.trash_id
|
||||
assert self.notebook.deleted == False
|
||||
|
||||
assert self.trash.object_id == self.trash_id
|
||||
assert datetime.now( tz = utc ) - self.trash.revision < self.delta
|
||||
assert self.trash.name == self.trash_name
|
||||
assert self.trash.read_write == False
|
||||
assert self.trash.trash_id == None
|
||||
assert self.trash.deleted == False
|
||||
|
||||
def test_set_name( self ):
|
||||
new_name = u"my new notebook"
|
||||
|
@ -44,11 +46,19 @@ class Test_notebook( object ):
|
|||
assert self.notebook.read_write == True
|
||||
assert self.notebook.revision == original_revision
|
||||
|
||||
def test_set_deleted( self ):
|
||||
previous_revision = self.notebook.revision
|
||||
self.notebook.deleted = True
|
||||
|
||||
assert self.notebook.deleted == True
|
||||
assert self.notebook.revision > previous_revision
|
||||
|
||||
def test_to_dict( self ):
|
||||
d = self.notebook.to_dict()
|
||||
|
||||
assert d.get( "name" ) == self.name
|
||||
assert d.get( "trash_id" ) == self.trash.object_id
|
||||
assert d.get( "read_write" ) == True
|
||||
assert d.get( "deleted" ) == self.notebook.deleted
|
||||
assert d.get( "object_id" ) == self.notebook.object_id
|
||||
assert datetime.now( tz = utc ) - d.get( "revision" ) < self.delta
|
||||
|
|
|
@ -39,6 +39,23 @@ function Wiki( invoker ) {
|
|||
alert( "Luminotes does not currently support the " + unsupported_agent + " web browser for editing. If possible, please use Firefox or Internet Explorer instead. " + unsupported_agent + " support will be added in a future release. Sorry for the inconvenience." );
|
||||
}
|
||||
|
||||
// if a notebook was just deleted, show a message with an undo button
|
||||
var deleted_id = getElement( "deleted_id" ).value;
|
||||
if ( deleted_id && this.notebook.read_write ) {
|
||||
var undo_button = createDOM( "input", {
|
||||
"type": "button",
|
||||
"class": "message_button",
|
||||
"value": "undo",
|
||||
"title": "undo deletion"
|
||||
} );
|
||||
var trash_link = createDOM( "a", {
|
||||
"href": "/notebooks/" + this.notebook.trash_id + "?parent_id=" + this.notebook.object_id
|
||||
}, "trash" );
|
||||
var message_div = this.display_message( "The notebook has been moved to the", [ trash_link, ". ", undo_button ] );
|
||||
var self = this;
|
||||
connect( undo_button, "onclick", function ( event ) { self.undelete_notebook_via_undo( event, deleted_id, message_div ); } );
|
||||
}
|
||||
|
||||
// populate the wiki with startup notes
|
||||
this.populate(
|
||||
evalJSON( getElement( "startup_notes" ).value || "null" ),
|
||||
|
@ -78,7 +95,7 @@ function Wiki( invoker ) {
|
|||
}
|
||||
|
||||
var rename = evalJSON( getElement( "rename" ).value );
|
||||
if ( rename )
|
||||
if ( rename && this.notebook.read_write )
|
||||
this.start_notebook_rename();
|
||||
}
|
||||
|
||||
|
@ -235,6 +252,14 @@ Wiki.prototype.populate = function ( startup_notes, current_notes, note_read_wri
|
|||
event.stop();
|
||||
} );
|
||||
}
|
||||
|
||||
var rename_notebook_link = getElement( "delete_notebook_link" );
|
||||
if ( rename_notebook_link ) {
|
||||
connect( rename_notebook_link, "onclick", function ( event ) {
|
||||
self.delete_notebook();
|
||||
event.stop();
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
Wiki.prototype.background_clicked = function ( event ) {
|
||||
|
@ -945,6 +970,13 @@ Wiki.prototype.undelete_editor_via_undelete = function( event, note_id, position
|
|||
event.stop();
|
||||
}
|
||||
|
||||
Wiki.prototype.undelete_notebook_via_undo = function( event, notebook_id, position_after ) {
|
||||
this.invoker.invoke( "/notebooks/undelete", "POST", {
|
||||
"notebook_id": notebook_id,
|
||||
} );
|
||||
|
||||
event.stop();
|
||||
}
|
||||
|
||||
Wiki.prototype.compare_versions = function( event, editor, previous_revision ) {
|
||||
this.clear_pulldowns();
|
||||
|
@ -1436,6 +1468,12 @@ Wiki.prototype.end_notebook_rename = function () {
|
|||
} );
|
||||
}
|
||||
|
||||
Wiki.prototype.delete_notebook = function () {
|
||||
this.invoker.invoke( "/notebooks/delete", "POST", {
|
||||
"notebook_id": this.notebook_id,
|
||||
} );
|
||||
}
|
||||
|
||||
Wiki.prototype.toggle_editor_changes = function ( event, editor ) {
|
||||
// if the pulldown is already open, then just close it
|
||||
var pulldown_id = "changes_" + editor.id;
|
||||
|
|
|
@ -84,6 +84,7 @@ function test_Wiki() {
|
|||
<input type="hidden" name="note" id="note" value="" />
|
||||
<input type="hidden" name="note_read_write" id="note_read_write" value="" />
|
||||
<input type="hidden" name="rename" id="rename" value="false" />
|
||||
<input type="hidden" name="deleted_id" id="deleted_id" value="" />
|
||||
|
||||
<div id="static_notes">
|
||||
</div>
|
||||
|
|
|
@ -55,6 +55,16 @@ class Link_area( Div ):
|
|||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
( notebook.name != u"trash" ) and Div(
|
||||
A(
|
||||
u"delete notebook",
|
||||
href = u"#",
|
||||
id = u"delete_notebook_link",
|
||||
title = u"Move this notebook to the trash.",
|
||||
),
|
||||
class_ = u"link_area_item",
|
||||
) or None,
|
||||
|
||||
notebook.trash_id and Div(
|
||||
A(
|
||||
u"trash",
|
||||
|
|
|
@ -28,6 +28,7 @@ class Main_page( Page ):
|
|||
http_url = None,
|
||||
conversion = None,
|
||||
rename = False,
|
||||
deleted_id = None,
|
||||
):
|
||||
startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ]
|
||||
|
||||
|
@ -96,6 +97,7 @@ class Main_page( Page ):
|
|||
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"rename", id = u"rename", value = json( rename ) ),
|
||||
Input( type = u"hidden", name = u"deleted_id", id = u"deleted_id", value = deleted_id ),
|
||||
Div(
|
||||
id = u"status_area",
|
||||
),
|
||||
|
|
Reference in New Issue