diff --git a/NEWS b/NEWS index f3d2be8..6bcdba9 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,8 @@ 1.6.8: ? * You can now print your entire notebook. Just click the "print" link on the left side of the page. + * You can now print an individual note. Just click the "options" tab on a + note and then "print this note". * Changed the order of exported HTML and CSV notebooks so that after all the "startup" notes are included, the remaining notes are included in alphabetical order (instead of reverse chronological order). diff --git a/controller/Notebooks.py b/controller/Notebooks.py index 1b0681d..dee43f8 100644 --- a/controller/Notebooks.py +++ b/controller/Notebooks.py @@ -1196,16 +1196,19 @@ class Notebooks( object ): @validate( notebook_id = Valid_id(), format = Valid_string( min = 1, max = 100 ), + note_id = Valid_id( none_okay = True ), user_id = Valid_id( none_okay = True ), ) - def export( self, notebook_id, format, user_id ): + def export( self, notebook_id, format, note_id = None, user_id = None ): """ Download the entire contents of the given notebook as a stand-alone file. @type notebook_id: unicode - @param notebook_id: id of notebook to download + @param notebook_id: id of notebook to export @type format: unicode @param format: string indicating the export plugin to use, currently one of: "html", "csv" + @type notebook_id: unicode + @param note_id: id of single note within the notebook to export (optional) @type user_id: unicode @param user_id: id of current logged-in user (if any), determined by @grab_user_id @rtype: unicode or generator (for streaming files) @@ -1221,9 +1224,16 @@ class Notebooks( object ): if not notebook: raise Access_error() - startup_notes = self.__database.select_many( Note, notebook.sql_load_startup_notes() ) - other_notes = self.__database.select_many( Note, notebook.sql_load_non_startup_notes() ) - notes = startup_notes + other_notes + if note_id: + note = self.__database.load( Note, note_id ) + if not note: + raise Access_error() + notes = [ note ] + notebook = None + else: + startup_notes = self.__database.select_many( Note, notebook.sql_load_startup_notes() ) + other_notes = self.__database.select_many( Note, notebook.sql_load_non_startup_notes() ) + notes = startup_notes + other_notes from plugins.Invoke import invoke @@ -1233,7 +1243,7 @@ class Notebooks( object ): plugin_name = format, database = self.__database, notebook = notebook, - notes = startup_notes + other_notes, + notes = notes, response_headers = cherrypy.response.headerMap, ) except ( ImportError, AttributeError ): diff --git a/controller/test/Test_notebooks.py b/controller/test/Test_notebooks.py index d5c1ce7..44d70b7 100644 --- a/controller/test/Test_notebooks.py +++ b/controller/test/Test_notebooks.py @@ -4801,6 +4801,45 @@ class Test_notebooks( Test_controller ): assert u"access" in result[ "body" ][ 0 ] + def test_export_print_with_note_id( self ): + self.login() + + note3 = Note.create( "55", u"

blah

foo", notebook_id = self.notebook.object_id ) + self.database.save( note3 ) + + result = self.http_get( + "/notebooks/export?notebook_id=%s&format=print¬e_id=%s" % ( self.notebook.object_id, note3.object_id ), + session_id = self.session_id, + ) + + assert result.get( "notebook" ) == None + assert result.get( "view" ) + + notes = result.get( "notes" ) + assert len( notes ) == 1 + note = notes[ 0 ] + + assert note.object_id == note3.object_id + assert note.revision == note3.revision + assert note.title == note3.title + assert note.contents == note3.contents + assert note.notebook_id == note3.notebook_id + assert note.startup == note3.startup + assert note.deleted_from_id == note3.deleted_from_id + assert note.rank == note3.rank + assert note.user_id == note3.user_id + assert note.creation == note3.creation + + def test_export_print_with_unknown_note_id( self ): + self.login() + + result = self.http_get( + "/notebooks/export?notebook_id=%s&format=print¬e_id=%s" % ( self.notebook.object_id, self.unknown_note_id ), + session_id = self.session_id, + ) + + assert u"access" in result[ "body" ][ 0 ] + def test_create( self ): self.login() diff --git a/plugins/export_html/Html_file.py b/plugins/export_html/Html_file.py index 9ab6d5d..27f3238 100644 --- a/plugins/export_html/Html_file.py +++ b/plugins/export_html/Html_file.py @@ -18,7 +18,7 @@ class Html_file( Html ): contents = self.IMAGE_PATTERN.sub( '', contents ) relinked_notes[ note.object_id ] = contents - response_headers[ u"Content-Disposition" ] = u"attachment; filename=%s.html" % notebook.friendly_id + response_headers[ u"Content-Disposition" ] = u"attachment; filename=%s.html" % notebook and notebook.friendly_id Html.__init__( self, diff --git a/plugins/export_print/Print_notes.py b/plugins/export_print/Print_notes.py index 77c17ed..4576092 100644 --- a/plugins/export_print/Print_notes.py +++ b/plugins/export_print/Print_notes.py @@ -19,11 +19,11 @@ class Print_notes( Html ): Style( file( u"static/css/download.css" ).read(), type = u"text/css" ), Style( file( u"static/css/print.css" ).read(), type = u"text/css" ), Meta( content = u"text/html; charset=UTF-8", http_equiv = u"content-type" ), - Title( notebook.name ), + Title( notebook and notebook.name or notes[ 0 ].title ), ), Body( Div( - H1( notebook.name ), + notebook and H1( notebook.name ) or None, [ Span( A( name = u"note_%s" % note.object_id ), Div( diff --git a/plugins/export_print/test/Test_export_print.py b/plugins/export_print/test/Test_export_print.py index f27e9bc..d809e84 100644 --- a/plugins/export_print/test/Test_export_print.py +++ b/plugins/export_print/test/Test_export_print.py @@ -73,3 +73,37 @@ class Test_export_html( object ): assert note.rank == expected_note.rank assert note.user_id == expected_note.user_id assert note.creation == expected_note.creation + + def test_export_print_without_notebook( self ): + note3 = Note.create( "55", u"

blah

foo", notebook_id = self.notebook.object_id ) + self.database.save( note3 ) + response_headers = {} + expected_notes = ( self.note1, self.note2, note3 ) + + result = invoke( + "export", + "print", + self.database, + None, + expected_notes, + response_headers, + ) + + # response headers should be unchanged + assert response_headers == {} + + notes = result.get( "notes" ) + assert len( notes ) == len( expected_notes ) + + # assert that the notes are in the expected order + for ( note, expected_note ) in zip( notes, expected_notes ): + assert note.object_id == expected_note.object_id + assert note.revision == expected_note.revision + assert note.title == expected_note.title + assert note.contents == expected_note.contents + assert note.notebook_id == expected_note.notebook_id + assert note.startup == expected_note.startup + assert note.deleted_from_id == expected_note.deleted_from_id + assert note.rank == expected_note.rank + assert note.user_id == expected_note.user_id + assert note.creation == expected_note.creation diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 3b14ef2..1d992c7 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -3217,9 +3217,19 @@ function Options_pulldown( wiki, notebook_id, invoker, editor ) { this.startup_label = createDOM( "label", { "for": "startup_checkbox", "class": "pulldown_label", "title": "Display this note whenever the notebook is loaded." }, "show on startup" ); + this.print_checkbox = createDOM( "input", { "type": "checkbox", "class": "pulldown_checkbox invisible" } ); + this.print_link = createDOM( "a", + { + "href": "/notebooks/export?notebook_id=" + notebook_id + "¬e_id=" + editor.id + "&format=print", + "target": "_new", + "class": "pulldown_link", + "title": "Print this note by itself." + }, + "print this note" + ); - appendChildNodes( this.div, this.startup_checkbox ); - appendChildNodes( this.div, this.startup_label ); + appendChildNodes( this.div, createDOM( "div", {}, this.startup_checkbox, this.startup_label ) ); + appendChildNodes( this.div, createDOM( "div", {}, this.print_checkbox, this.print_link ) ); this.startup_checkbox.checked = editor.startup; var self = this;