[snowy] Let user export data to JSON or tar of .note files
- From: Sanford Armstrong <sharm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [snowy] Let user export data to JSON or tar of .note files
- Date: Thu, 23 Dec 2010 11:58:53 +0000 (UTC)
commit fafd5d14824f676130c62f596a9f9601bf59b2e8
Author: Tony Young <rofflwaffls gmail com>
Date: Wed Dec 22 12:32:08 2010 +1300
Let user export data to JSON or tar of .note files
https://bugzilla.gnome.org/show_bug.cgi?id=629590
accounts/templates/accounts/preferences.html | 12 +++
accounts/urls.py | 2 +
export/tests.py | 95 ++++++++++++++++++++
export/urls.py | 24 +++++
export/views.py | 123 ++++++++++++++++++++++++++
settings.py | 1 +
urls.py | 2 +
7 files changed, 259 insertions(+), 0 deletions(-)
---
diff --git a/accounts/templates/accounts/preferences.html b/accounts/templates/accounts/preferences.html
index 7a322a6..b82fe58 100644
--- a/accounts/templates/accounts/preferences.html
+++ b/accounts/templates/accounts/preferences.html
@@ -94,5 +94,17 @@
<input type="hidden" name="openid_form" value="1" />
</form>
+<h3>{% trans "Export Notes" %}</h3>
+<table class="input-form">
+ <tr>
+ <th><a href="{% url note_api_index username=request.user %}?include_notes=true">{% trans "JSON (see API docs)" %}</a></th>
+ {% comment %}
+ {# Providing API output in XML format may just be confusing #}
+ <th><a href="{% url note_api_index username=request.user %}?include_notes=true&format=xml">{% trans "XML" %}</a></th>
+ {% endcomment %}
+ <th><a href="{% url export-tar %}">{% trans "Tarball of .note files (EXPERIMENTAL!)" %}</a></th>
+ </tr>
+</table>
+
{#<h3>{% trans "Registered Applications" %}</h3>#}
{% endblock %}
diff --git a/accounts/urls.py b/accounts/urls.py
index 281b554..374fad5 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -27,6 +27,8 @@ from registration.views import register
import snowy.accounts.views
import django_openid_auth.views
+import snowy.export.views
+
urlpatterns = patterns('',
url(r'^preferences/$', 'snowy.accounts.views.accounts_preferences',
name='preferences'),
diff --git a/export/__init__.py b/export/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/export/models.py b/export/models.py
new file mode 100644
index 0000000..e69de29
diff --git a/export/tests.py b/export/tests.py
new file mode 100644
index 0000000..1ba1e31
--- /dev/null
+++ b/export/tests.py
@@ -0,0 +1,95 @@
+#
+# Copyright (c) 2010 Tony Young <rofflwaffls gmail com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from django.test import TestCase
+
+import snowy.export.views
+
+import datetime
+import re
+import tarfile
+from xml.dom.minidom import parseString, parse
+
+try:
+ from cStringIO import StringIO
+except:
+ from StringIO import StringIO
+
+class fake_request(object):
+ class user:
+ @staticmethod
+ def is_authenticated():
+ return True
+
+class ExportTest(TestCase):
+ def setUp(self):
+ # monkey patch snowy.export._get_data to return what we want
+ self.data = [
+ {
+ "guid" : "00000000-0000-0000-0000-000000000000",
+ "note-content": u"here is a note <bold>with random tags</bold>ã??ï½?ï½?ï½?ã??ï½?ï½?ï½?ï½?ï½?ï½?ï½?",
+ "open-on-startup": True,
+ "last-metadata-change-date": "1970-01-01T13:00:00Z",
+ "title": u"note 壱",
+ "tags": [ u"herë", "are", "some", "tags" ],
+ "create-date": "1970-01-01T13:00:00Z",
+ "last-change-date": "1970-01-01T13:00:00Z"
+ },
+ {
+ "guid" : "00000000-0000-0000-0000-000000000001",
+ "note-content": u"here is another note with äçcèñts",
+ "open-on-startup": False,
+ "last-metadata-change-date": "1970-01-01T13:00:00Z",
+ "title": u"ã??ã?¼ã?? 2",
+ "tags": [ "here", "are", "some", "tags", "too" ],
+ "create-date": "1970-01-01T13:00:00Z",
+ "last-change-date": "1970-01-01T13:00:00Z"
+ }
+ ]
+
+ self.grouped_data = dict([
+ (note["guid"], note) for note in self.data
+ ])
+
+ def _get_data(request):
+ return self.data
+ snowy.export.views._get_data = _get_data
+
+ def _assert_xml(self, note_node, data):
+ tag_wrap_expr = re.compile(r"\<.+?\>(.*)\</.+?\>", re.MULTILINE | re.DOTALL)
+
+ guid = note_node.getAttribute("guid")
+ for child_node in note_node.childNodes:
+ tag = child_node.tagName
+
+ content = child_node.toxml()
+ content = tag_wrap_expr.match(content).group(1)
+
+ if tag == "text":
+ self.assertEquals(tag_wrap_expr.match(child_node.childNodes[0].toxml()).group(1), "%s\n\n%s" % (data["title"], data["note-content"]))
+ elif tag == "tags":
+ self.assertEquals([ tag.childNodes[0].toxml() for tag in child_node.childNodes ], data["tags"])
+ elif tag == "open-on-startup":
+ self.assertEquals(content == "True" and True or False, data[tag])
+ else:
+ self.assertEquals(content, data[tag])
+
+ def test_tar_export(self):
+ data = tarfile.TarFile(fileobj=StringIO(snowy.export.views.export_tar(fake_request).content), mode="r")
+ for info in data:
+ doc = parse(data.extractfile(info.name))
+ self._assert_xml(doc.childNodes[0], self.grouped_data[info.name.split(".")[0]])
diff --git a/export/urls.py b/export/urls.py
new file mode 100644
index 0000000..0d756da
--- /dev/null
+++ b/export/urls.py
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2010 Tony Young <rofflwaffls gmail com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from django.conf.urls.defaults import *
+
+import snowy.export.views
+
+urlpatterns = patterns('',
+ url(r'^tar', snowy.export.views.export_tar, name='export-tar'),
+)
diff --git a/export/views.py b/export/views.py
new file mode 100644
index 0000000..a022b1b
--- /dev/null
+++ b/export/views.py
@@ -0,0 +1,123 @@
+#
+# Copyright (c) 2010 Tony Young <rofflwaffls gmail com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from django.http import HttpResponse
+
+from django.contrib.auth.models import User
+from django.contrib.auth.decorators import login_required
+
+from snowy.api.handlers import describe_note
+from snowy.notes.models import Note
+
+import tarfile
+
+import time
+from dateutil.parser import parse as parse_iso_time
+
+from xml.dom.minidom import Document, parseString
+
+try:
+ from cStringIO import StringIO
+except:
+ from StringIO import StringIO
+
+# list of allowed fields
+ALLOWED_FIELDS = [ "title", "note-content", "last-change-date", "last-metadata-change-date", "create-date", "tags", "open-on-startup" ]
+
+def _get_data(request):
+ notes = Note.objects.user_viewable(request.user, User.objects.get(username=request.user))
+ return [describe_note(n) for n in notes]
+
+def _note_to_xml(doc, note):
+ root = doc.createElement("note")
+ root.setAttribute("xmlns", "http://beatniksoftware.com/tomboy")
+ root.setAttribute("xmlns:link", "http://beatniksoftware.com/tomboy/link")
+ root.setAttribute("xmlns:size", "http://beatniksoftware.com/tomboy/size")
+ root.setAttribute("version", "0.3")
+
+ for field in ALLOWED_FIELDS:
+ if field == "note-content":
+ wrap_elem = doc.createElement("text")
+ wrap_elem.setAttribute("xml:space", "preserve")
+
+ # make expat parse nicely
+ subdoc = parseString('<note-content xmlns:link="urn:tempuri" xmlns:size="urn:tempuri">%s\n\n%s</note-content>' % (
+ note["title"].encode("utf-8"),
+ note[field].encode("utf-8")
+ ))
+
+ elem = subdoc.documentElement
+
+ # quietly get rid of temporary namespaces
+ elem.removeAttribute("xmlns:link")
+ elem.removeAttribute("xmlns:size")
+
+ elem.setAttribute("version", "0.1")
+
+ wrap_elem.appendChild(elem)
+ elem = wrap_elem
+ else:
+ elem = doc.createElement(field)
+
+ if field == "tags":
+ for tag in note[field]:
+ tag_elem = doc.createElement("tag")
+ tag_elem.appendChild(doc.createTextNode(tag))
+ elem.appendChild(tag_elem)
+ else:
+ content = note[field]
+ if not isinstance(content, unicode):
+ content = str(content)
+ elem.appendChild(doc.createTextNode(content))
+
+ root.appendChild(elem)
+
+ return root
+
+ login_required
+def export_tar(request):
+ data = _get_data(request)
+
+ arch_file = StringIO()
+ arch = tarfile.TarFile(fileobj=arch_file, mode="w")
+
+ for note in data:
+ doc = Document()
+ root = _note_to_xml(doc, note)
+ doc.appendChild(root)
+
+ note_data = doc.toxml(encoding='utf-8')
+
+ note_file = StringIO()
+ note_file.write(note_data)
+ note_file.seek(0)
+
+ note_info = tarfile.TarInfo("%s.note" % note["guid"])
+ note_info.size = len(note_data)
+ note_info.mtime = time.mktime(parse_iso_time(note["last-change-date"]).timetuple())
+
+ arch.addfile(
+ tarinfo=note_info,
+ fileobj=note_file
+ )
+
+ arch.close()
+
+ response = HttpResponse(arch_file.getvalue())
+ response["Content-Type"] = "application/x-tar"
+ response["Content-Disposition"] = "attachment; filename=snowy-%s-%s.tar" % (request.user, time.strftime("%Y-%m-%d"))
+ return response
diff --git a/settings.py b/settings.py
index e21dc06..6c5ccc0 100644
--- a/settings.py
+++ b/settings.py
@@ -127,6 +127,7 @@ INSTALLED_APPS = [
'django_openid_auth',
'notes',
'mobile_notes',
+ 'export',
# System apps
'django.contrib.admin',
diff --git a/urls.py b/urls.py
index 923cfb7..79544d1 100644
--- a/urls.py
+++ b/urls.py
@@ -33,6 +33,8 @@ urlpatterns = patterns('',
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/', include(admin.site.urls)),
+ (r'^export/', include('snowy.export.urls')),
+
(r'^mobile/', include('snowy.mobile_notes.urls')),
url(r'^(?P<username>\w+)/$', 'snowy.views.user_index', name="user_index"),
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]