witten
/
luminotes
Archived
1
0
Fork 0

Several minor improvements to CSV importing and exporting.

This commit is contained in:
Dan Helfman 2009-02-20 15:22:23 -08:00
parent f72297be32
commit e6fbbfec41
4 changed files with 34 additions and 4 deletions

1
NEWS
View File

@ -2,6 +2,7 @@
* When you export your notebook as an HTML or CSV file, the saved filename is * When you export your notebook as an HTML or CSV file, the saved filename is
now based on the name of your notebook, for instance "my-to-do-list.html" now based on the name of your notebook, for instance "my-to-do-list.html"
or "ideas-for-my-novel.csv". or "ideas-for-my-novel.csv".
* Several minor improvements to CSV importing and exporting.
* Converted the existing HTML and CSV export features to work as separate * Converted the existing HTML and CSV export features to work as separate
export plugins. This means that a new export format can be implemented as export plugins. This means that a new export format can be implemented as
a new plugin. a new plugin.

View File

@ -1,18 +1,30 @@
import re
import csv import csv
from cStringIO import StringIO from cStringIO import StringIO
from model.User import User from model.User import User
FILE_LINK_PATTERN = re.compile( u'<a\s+href="[^"]*\/files\/download\?file_id=[^"]+"[^>]*>', re.IGNORECASE )
IMAGE_PATTERN = re.compile( u'<img [^>]* ?/?>', re.IGNORECASE )
NEWLINE_PATTERN = re.compile( u'[\r\n]+' )
def export( database, notebook, notes, response_headers ): def export( database, notebook, notes, response_headers ):
""" """
Format the given notes as a CSV file and return it as a streaming generator. Format the given notes as a CSV file and return it as a streaming generator.
""" """
buffer = StringIO() buffer = StringIO()
writer = csv.writer( buffer ) writer = csv.writer( buffer, quoting = csv.QUOTE_NONNUMERIC )
response_headers[ u"Content-Disposition" ] = u"attachment; filename=%s.csv" % notebook.friendly_id response_headers[ u"Content-Disposition" ] = u"attachment; filename=%s.csv" % notebook.friendly_id
response_headers[ u"Content-Type" ] = u"text/csv;charset=utf-8" response_headers[ u"Content-Type" ] = u"text/csv;charset=utf-8"
def prepare_contents( contents ):
contents = FILE_LINK_PATTERN.sub( '<a>', contents )
contents = IMAGE_PATTERN.sub( '', contents )
contents = NEWLINE_PATTERN.sub( ' ', contents )
return contents.strip()
def stream(): def stream():
writer.writerow( ( u"contents", u"title", u"note_id", u"startup", u"username", u"revision_date" ) ) writer.writerow( ( u"contents", u"title", u"note_id", u"startup", u"username", u"revision_date" ) )
yield buffer.getvalue() yield buffer.getvalue()
@ -24,7 +36,7 @@ def export( database, notebook, notes, response_headers ):
user = database.load( User, note.user_id ) user = database.load( User, note.user_id )
writer.writerow( ( writer.writerow( (
note.contents and note.contents.strip().encode( "utf8" ) or None, note.contents and prepare_contents( note.contents ).encode( "utf8" ) or None,
note.title and note.title.strip().encode( "utf8" ) or None, note.title and note.title.strip().encode( "utf8" ) or None,
note.object_id, note.object_id,
note.startup and 1 or 0, note.startup and 1 or 0,

View File

@ -41,7 +41,7 @@ class Test_export_csv( object ):
self.note2 = Note.create( note_id, u"<h3>other title</h3>whee", notebook_id = self.notebook.object_id, user_id = self.user.object_id ) self.note2 = Note.create( note_id, u"<h3>other title</h3>whee", notebook_id = self.notebook.object_id, user_id = self.user.object_id )
self.database.save( self.note2, commit = False ) self.database.save( self.note2, commit = False )
def test_export_csv( self, note_contents = None ): def test_export_csv( self, note_contents = None, expected_contents = None ):
if not note_contents: if not note_contents:
note_contents = u"<h3>blah</h3>foo" note_contents = u"<h3>blah</h3>foo"
@ -87,7 +87,10 @@ class Test_export_csv( object ):
expected_note = expected_notes[ note_count ] expected_note = expected_notes[ note_count ]
assert expected_note assert expected_note
assert contents.decode( "utf8" ) == expected_note.contents.strip() if expected_contents and note_id == note3.object_id:
assert contents.decode( "utf8" ) == expected_contents.replace( "\n", " " ).strip()
else:
assert contents.decode( "utf8" ) == expected_note.contents.replace( "\n", " " ).strip()
if expected_note.title: if expected_note.title:
assert title.decode( "utf8" ) == expected_note.title.strip() assert title.decode( "utf8" ) == expected_note.title.strip()
@ -115,6 +118,18 @@ class Test_export_csv( object ):
def test_export_csv_with_trailing_newline_in_contents( self ): def test_export_csv_with_trailing_newline_in_contents( self ):
self.test_export_csv( note_contents = u"<h3>blah</h3>foo\n" ) self.test_export_csv( note_contents = u"<h3>blah</h3>foo\n" )
def test_export_csv_with_file_attachment_in_contents( self ):
self.test_export_csv(
note_contents = u"<h3>blah</h3>foo<a href=\"/files/download?file_id=blah&quote_filename=False\">file</a>",
expected_contents = "<h3>blah</h3>foo<a>file</a>",
)
def test_export_csv_with_image_in_contents( self ):
self.test_export_csv(
note_contents = u"<h3>blah</h3>foo<a href=\"/files/download?file_id=blah&quote_filename=False\"><img src=\"whee.png\" /></a>",
expected_contents = "<h3>blah</h3>foo<a></a>",
)
def test_export_csv_with_blank_username( self ): def test_export_csv_with_blank_username( self ):
self.user._User__username = None self.user._User__username = None
self.database.save( self.user ) self.database.save( self.user )

View File

@ -5,6 +5,7 @@ from view.Tags import Html, Head, Title, Style, Meta, Body, H1, Div, Span, Hr, A
class Html_file( Html ): class Html_file( Html ):
NOTE_LINK_PATTERN = re.compile( u'<a\s+href="[^"]*(?:\/notebooks\/)?[^>]+[?&]note_id=([a-z0-9]*)"[^>]*>', re.IGNORECASE ) NOTE_LINK_PATTERN = re.compile( u'<a\s+href="[^"]*(?:\/notebooks\/)?[^>]+[?&]note_id=([a-z0-9]*)"[^>]*>', re.IGNORECASE )
FILE_LINK_PATTERN = re.compile( u'<a\s+href="[^"]*\/files\/download\?file_id=[^"]+"[^>]*>', re.IGNORECASE )
IMAGE_PATTERN = re.compile( u'<img [^>]* ?/?>', re.IGNORECASE ) IMAGE_PATTERN = re.compile( u'<img [^>]* ?/?>', re.IGNORECASE )
def __init__( self, notebook, notes ): def __init__( self, notebook, notes ):
@ -14,6 +15,7 @@ class Html_file( Html ):
# images since they're not presently included with the download # images since they're not presently included with the download
for note in notes: for note in notes:
contents = self.NOTE_LINK_PATTERN.sub( r'<a href="#note_\1">', note.contents ) contents = self.NOTE_LINK_PATTERN.sub( r'<a href="#note_\1">', note.contents )
contents = self.FILE_LINK_PATTERN.sub( '<a>', contents )
contents = self.IMAGE_PATTERN.sub( '', contents ) contents = self.IMAGE_PATTERN.sub( '', contents )
relinked_notes[ note.object_id ] = contents relinked_notes[ note.object_id ] = contents