Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export options and user deletion options for Snowy. #1

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
12 changes: 11 additions & 1 deletion accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#

from django.contrib.auth.models import User
from django.forms import ModelChoiceField
from django.forms import ModelChoiceField, BooleanField
from registration.forms import RegistrationFormUniqueEmail
from django.utils.translation import ugettext_lazy as _
from recaptcha_django import ReCaptchaField
Expand Down Expand Up @@ -109,3 +109,13 @@ def __init__(self, *args, **kwargs):
super(RemoveUserOpenIDForm, self).__init__(*args, **kwargs)

self.fields['openid'] = UserOpenIDChoiceField(open_ids, required=True, label=_('Delete OpenID account'))

class ResetUserForm(forms.Form):
def __init__(self, *args, **kwargs):
super(ResetUserForm, self).__init__(*args, **kwargs)
self.fields['confirm_reset'] = BooleanField(required=True, label=_('Confirm account reset'))

class DeleteUserForm(forms.Form):
def __init__(self, *args, **kwargs):
super(DeleteUserForm, self).__init__(*args, **kwargs)
self.fields['confirm_delete'] = BooleanField(required=True, label=_('Confirm account deletion'))
70 changes: 63 additions & 7 deletions accounts/templates/accounts/preferences.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,27 @@
// clear the password form
$(document).ready(function() {
$('.input-form input[type="password"]').val("")

$(".reset-confirm").hide();
$(".delete-confirm").hide();

$("#reset-btn").bind("click", function(evt) {
$(".reset-confirm").fadeIn();
$(this).unbind("click");
evt.preventDefault();
});

$("#delete-btn").bind("click", function(evt) {
$(".delete-confirm").fadeIn();
$(this).unbind("click");
evt.preventDefault();
});
});
</script>
{% endblock %}

{% block content %}

{% if messages %}
{% for message in messages %}
<div{% if message.tags %} class="{{ message.tags }} center"{% endif %} style="color: red;">{{ message }}</div>
<br />
{% endfor %}
{% endif %}

<h1>{% trans "Preferences" %}</h1>
{% if user.has_usable_password %}
<h3>{% trans "Change your password" %}</h3>
Expand Down Expand Up @@ -94,5 +102,53 @@ <h3>{% trans "OpenID Accounts" %}</h3>
<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" %}</a></th>
<th><a href="{% url note_api_index username=request.user %}?include_notes=true&format=xml">{% trans "XML" %}</a></th>
<th><a href="{% url export-tar %}">{% trans "Tarball" %}</a></th>
</tr>
</table>


<h3>{% trans "Reset All Data" %}</h3>
<div class="reset-confirm" style="color: red;">{% trans "Warning: This action is irreversible! All of your synced notes and notebooks will be destroyed!" %}</div>
<form method="POST">
<table class="input-form">
<tbody class="reset-confirm">
{{ reset_form.as_table }}
</tbody>
<tfoot>
<tr>
<th></th>
<td>
<input type="submit" id="reset-btn" value="{% trans "Reset User" %}"/>
</td>
</tr>
</tfoot>
</table>
<input type="hidden" name="reset_form" value="1" />
</form>

<h3>{% trans "Permanently Delete This User" %}</h3>
<div class="delete-confirm" style="color: red;">{% trans "Warning: This action is irreversible! Any data associated with your account will be destroyed!" %}</div>
<form method="POST">
<table class="input-form">
<tbody class="delete-confirm">
{{ delete_form.as_table }}
</tbody>
<tfoot>
<tr>
<th></th>
<td>
<input type="submit" id="delete-btn" value="{% trans "Goodbye, World!" %}"/>
</td>
</tr>
</tfoot>
</table>
<input type="hidden" name="delete_form" value="1" />
</form>

{#<h3>{% trans "Registered Applications" %}</h3>#}
{% endblock %}
2 changes: 2 additions & 0 deletions accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
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'),
Expand Down
32 changes: 29 additions & 3 deletions accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from django.utils.translation import ugettext_lazy as _

from django.contrib.auth import get_backends
from django.contrib.auth import get_backends, logout
from django.contrib.auth.forms import UserChangeForm, PasswordChangeForm
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, REDIRECT_FIELD_NAME
Expand All @@ -32,7 +32,8 @@
from django.conf import settings

from snowy.accounts.models import UserProfile
from snowy.accounts.forms import InternationalizationForm, OpenIDRegistrationFormUniqueUser, EmailChangeForm, RemoveUserOpenIDForm
from snowy.accounts.forms import InternationalizationForm, OpenIDRegistrationFormUniqueUser, EmailChangeForm, RemoveUserOpenIDForm, DeleteUserForm, ResetUserForm
from snowy.core.utils import create_uuid

from django_openid_auth import auth
from django_openid_auth.auth import OpenIDBackend
Expand Down Expand Up @@ -162,11 +163,36 @@ def accounts_preferences(request, template_name='accounts/preferences.html'):
openid_form.cleaned_data['openid'].delete()
else:
openid_form = RemoveUserOpenIDForm(open_ids=open_ids)

if 'reset_form' in request.POST:
reset_form = ResetUserForm(request.POST)
if reset_form.is_valid():
request.user.note_set.all().delete()
request.user.notetag_set.all().delete()

profile = request.user.userprofile_set.all()[0]
profile.latest_sync_rev = -1
profile.current_sync_uuid = create_uuid()
profile.save()

messages.add_message(request, messages.SUCCESS, _("User data reset"))
reset_form = ResetUserForm()

if 'delete_form' in request.POST:
delete_form = DeleteUserForm(request.POST)
if delete_form.is_valid():
user.delete()
messages.add_message(request, messages.SUCCESS, _("Account deleted"))
logout(request)
return HttpResponseRedirect(reverse('snowy_index'))
delete_form = DeleteUserForm()

return render_to_response(template_name,
{'user': user, 'i18n_form': i18n_form,
'password_form': password_form,
'email_form' : email_form,
'open_ids' : open_ids,
'openid_form' : openid_form},
'openid_form' : openid_form,
'reset_form' : reset_form,
'delete_form' : delete_form},
context_instance=RequestContext(request))
Empty file added export/__init__.py
Empty file.
Empty file added export/models.py
Empty file.
95 changes: 95 additions & 0 deletions export/tests.py
Original file line number Diff line number Diff line change
@@ -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> and unicode",
"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]])
24 changes: 24 additions & 0 deletions export/urls.py
Original file line number Diff line number Diff line change
@@ -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'),
)
Loading