Skip to content

Commit

Permalink
Merge pull request #557 from llewelld/iss536_account_deletion
Browse files Browse the repository at this point in the history
Add an account deletion option
  • Loading branch information
llewelld authored Jul 31, 2023
2 parents 8d891a4 + 496cc0e commit d82f22a
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 2 deletions.
4 changes: 4 additions & 0 deletions server/apps/users/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,7 @@ class UserProfileForm(forms.Form):
widget=forms.CheckboxInput(attrs={"class": "custom-control-input"}))
profile_submitted.group = "hidden"

class UserProfileDeleteForm(forms.Form):
delete_oh_data = forms.BooleanField(label = "Delete your stories from OpenHumans",
required=False,
widget=forms.CheckboxInput(attrs={"class": "custom-control-input"}))
21 changes: 21 additions & 0 deletions server/apps/users/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib.auth.models import User
from .models import UserProfile
from server.apps.main.models import PublicExperience

def user_profile_exists(user):
"""
Expand Down Expand Up @@ -53,3 +54,23 @@ def get_user_profile(user):
except UserProfile.DoesNotExist:
uo = None
return uo

def delete_user(user, delete_oh_data):
"""
Deletes the user and all data associated with it.
Args:
delete_oh_data: True if stories on OpenHumans should also be deleted
Returns:
None
"""
ohmember = user.openhumansmember

# Delete the stories from the OpenHumans database
if delete_oh_data:
ohmember.delete_all_files()

# Delete the actual user
user.delete()

44 changes: 44 additions & 0 deletions server/apps/users/templates/users/delete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends 'main/application.html' %}

{% block title %}AutSPACEs - {{title}} {% endblock %}

{% load static %}
{% load custom_tags %}
{% load humanize %}

{% block content %}

<!-- Delete User Profile Form -->
<section id="delete-user-profile-form">
<div class="container profile-section">
<h3><label>Delete AutSPACEs account</label></h3>
<p/><a href="https://www.openhumans.org/">OpenHumans</a> ID: {{ oh_id }}

<p/>Are you sure you want to delete your AutSPACEs account?
<p/>This will remove all your data from the AutSPACEs platform. This process cannot be undone.
<p/>Select the switch below to also remove your stories from OpenHumans.
<form action="{% url 'users:delete' %}" class="form-context" method="post">
{% csrf_token %}
<div class="form-group">
{% for field in form %}
<div class="row">
<div class="form-check col-lg-12">
<div class="custom-control custom-switch">
{{ field }}
<label class="custom-control-label" for="{{ field.auto_id}}">{{ field.label }}</label>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary" id="submitForm">Delete account</button>
<a role="button" class="btn btn-secondary ml-5" id="cancelForm" href="{% url 'users:profile' %}">Cancel</a>
</div>
</form>
</div>
</section>

{% endblock %}


31 changes: 31 additions & 0 deletions server/apps/users/templates/users/goodbye.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% extends 'main/application.html' %}

{% block title %}AutSPACEs - {{title}} {% endblock %}

{% load static %}
{% load custom_tags %}
{% load humanize %}

{% block content %}

<!-- User Profile Deleted Page -->
<section id="goodbye-notification">
<div class="container profile-section">
<h3><label>Thank you for contributing to AutSPACEs</label></h3>

<p/>We're sorry to see you go, but are grateful for your contribution to AutSPACEs.
{% if delete_oh_data %}
<p/>All of your personal data has been removed from the AutSPACEs platform and your stories have been removed from <a href="https://www.openhumans.org/">OpenHumans</a>. This won't affect any other data you may have stored on the OpenHumans platform.
{% else %}
<p/>All of your personal data has been removed from the AutSPACEs platform, but please be aware that stories you entered here may still be stored on <a href="https://www.openhumans.org/">OpenHumans</a>. You'll need to delete these separately.
{% endif %}
<p/>If you'd like to contribute to AutSPACEs again in the future, please feel free to create a new account.
<div class="form-group">
<a role="button" class="btn btn-primary" id="return" href="{% url 'index' %}">Return to front page</a>
</div>
</div>
</section>

{% endblock %}


2 changes: 2 additions & 0 deletions server/apps/users/templates/users/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
<div class="container profile-section">
<h3><label>Account</label></h3>
<p/><a href="https://www.openhumans.org/">OpenHumans</a> ID: {{ oh_id }}
<p/><a href="{% url 'users:delete' %}">Delete AutSPACEs account</a>

<form action="{% url 'users:profile' %}" class="form-context" method="post">
{% csrf_token %}
{% regroup form by field.group as field_groups %}
Expand Down
92 changes: 92 additions & 0 deletions server/apps/users/tests/fixtures/delete_user.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
interactions:
- request:
body: project_member_id=39896706&all_files=True
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '41'
Content-Type:
- application/x-www-form-urlencoded
User-Agent:
- python-requests/2.31.0
method: POST
uri: https://www.openhumans.org/api/direct-sharing/project/files/delete/
response:
body:
string: '{"ids":[69072773]}'
headers:
Allow:
- POST, OPTIONS
Cache-Control:
- max-age=0, no-cache, no-store, must-revalidate
Connection:
- keep-alive
Content-Length:
- '18'
Content-Type:
- application/json
Date:
- Fri, 28 Jul 2023 15:06:45 GMT
Expires:
- Fri, 28 Jul 2023 15:06:45 GMT
Server:
- gunicorn/20.0.4
Vary:
- Accept, Authorization, Cookie, Origin
Via:
- 1.1 vegur
X-Frame-Options:
- SAMEORIGIN
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '0'
User-Agent:
- python-requests/2.31.0
method: POST
uri: https://www.openhumans.org/api/direct-sharing/project/remove-members/
response:
body:
string: '"success"'
headers:
Allow:
- POST, OPTIONS
Cache-Control:
- max-age=0, no-cache, no-store, must-revalidate
Connection:
- keep-alive
Content-Length:
- '9'
Content-Type:
- application/json
Date:
- Fri, 28 Jul 2023 15:06:46 GMT
Expires:
- Fri, 28 Jul 2023 15:06:46 GMT
Server:
- gunicorn/20.0.4
Vary:
- Accept, Authorization, Cookie, Origin
Via:
- 1.1 vegur
X-Frame-Options:
- SAMEORIGIN
status:
code: 200
message: OK
version: 1
149 changes: 149 additions & 0 deletions server/apps/users/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.test import TestCase
from django.conf import settings
from django.test import Client
from django.contrib.auth.models import User

from openhumans.models import OpenHumansMember

Expand All @@ -9,6 +10,12 @@
user_profile_exists,
user_submitted_profile,
)
from server.apps.main.models import (
PublicExperience,
ExperienceHistory,
)

import vcr

class ViewTests(TestCase):
"""
Expand Down Expand Up @@ -196,3 +203,145 @@ def test_profile_rendering(self):
self.assertContains(response, "abcdabcd")
self.assertContains(response, "26-35")

def test_user_delete_rendering(self):
"""
Check that the user delete page is rendered correctly.
"""
c = Client()
c.force_login(self.user_b)
response = c.get("/users/delete/")
assert response.status_code == 200
self.assertTemplateUsed(response, 'users/delete.html')

def test_user_delete_logged_out(self):
"""
Check that the user delete page is not shown if the user isn't logged in.
"""
c = Client()
response = c.get("/users/delete/", follow=True)
self.assertRedirects(response, "/",
status_code=302, target_status_code=200)
self.assertTemplateUsed(response, 'main/home.html')

def delete_user(self, delete_oh_data):
"""
Helper function that deletes a user and checks the result.
"""
# Create a user for us to delete
data = {"access_token": "123456", "refresh_token": "bar", "expires_in": 36000}
oh = OpenHumansMember.create(oh_id=97526814, data=data)
oh.save()
user = oh.user
user.openhumansmember = oh
user.set_password("password")
user.save()
# Create a user profile
up_data = {
"profile_submitted": False,
"autistic_identification": "unspecified",
"age_bracket": "18-25",
"age_public": False,
"gender": "see_description",
"gender_self_identification": "",
"gender_public": False,
"description": "Timelord",
"description_public": False,
"comms_review": False,
"abuse": False,
"violence": False,
"drug": True,
"mentalhealth": False,
"negbody": True,
"other": False,
}
up = UserProfile.objects.create(user=user, **up_data)
# Create a story
pe_data = {
"experience_text": "Here is some experience text",
"difference_text": "Here is some difference text",
"title_text": "Here is the title",
}
pe = PublicExperience.objects.create(
open_humans_member=oh, experience_id="69072773", **pe_data
)
# Create a history entry
mh_data = {
"changed_by": oh,
"change_comments": "Local moderation comment",
"change_reply": "Moderation comment",
}
self.eh_a = ExperienceHistory.objects.create(
experience = pe, **mh_data
)

objects = PublicExperience.objects.filter(
open_humans_member=oh
)
assert len(objects) > 0

objects = UserProfile.objects.filter(
user=user
)
assert len(objects) > 0

objects = User.objects.filter(
id=user.id
)
assert len(objects) > 0

objects = ExperienceHistory.objects.filter(
experience_id="69072773"
)
assert len(objects) > 0

c = Client()
c.force_login(user)
response = c.post(
"/users/delete/",
{
"title": "Profile Deleted",
"delete_oh_data": delete_oh_data,
},
follow=True,
)
assert response.status_code == 200

objects = PublicExperience.objects.filter(
open_humans_member=oh
)
assert len(objects) == 0

objects = UserProfile.objects.filter(
user=user
)
assert len(objects) == 0

objects = User.objects.filter(
id=user.id
)
assert len(objects) == 0

objects = ExperienceHistory.objects.filter(
experience_id="69072773"

)
assert len(objects) == 0

def test_user_delete_no_oh(self):
"""
Test that profile deletion works, without deleting the OpenHumans data.
"""
self.delete_user(False)

@vcr.use_cassette(
'server/apps/users/tests/fixtures/delete_user.yaml',
record_mode="none",
filter_query_parameters=['access_token'],
match_on=['path'],
)
def test_user_delete_oh(self):
"""
Test that profile deletion works, including deleting OpenHumans data.
"""
self.delete_user(True)

1 change: 1 addition & 0 deletions server/apps/users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
urlpatterns = [
path("profile/", views.user_profile, name="profile"),
path("greetings/", views.user_profile, {"first_visit": True}, name="greetings"),
path("delete/", views.user_profile_delete, name="delete"),
]
Loading

0 comments on commit d82f22a

Please sign in to comment.