Skip to content

Commit

Permalink
Add an account deletion option
Browse files Browse the repository at this point in the history
Adds an option to delete the user's account to the profile page.

Fixes #536.

Co-authored-by: Bastian Greshake Tzovaras <[email protected]>
  • Loading branch information
llewelld and gedankenstuecke committed Jul 31, 2023
1 parent 8d891a4 commit 496cc0e
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 496cc0e

Please sign in to comment.