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

Fix/remove user/soft removal #1724

Merged
merged 7 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/apps/api/views/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ class CompetitionParticipantViewSet(ModelViewSet):
queryset = CompetitionParticipant.objects.all()
serializer_class = CompetitionParticipantSerializer
filter_backends = (DjangoFilterBackend, SearchFilter)
filter_fields = ('user__username', 'user__email', 'status', 'competition')
filter_fields = ('user__username', 'user__email', 'status', 'competition', 'user__is_deleted')
search_fields = ('user__username', 'user__email',)

def get_queryset(self):
Expand Down
1 change: 1 addition & 0 deletions src/apps/profiles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ def delete(self, *args, **kwargs):
# Mark the user as deleted
self.is_deleted = True
self.deleted_at = now()
self.is_active = False

# Anonymize or removed personal data
user_email = self.email # keep track of the email for the end of the procedure
Expand Down
45 changes: 27 additions & 18 deletions src/apps/profiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from datasets.models import Data, DataGroup
from tasks.models import Task
from forums.models import Post
from utils.email import codalab_send_mail


class LoginView(auth_views.LoginView):
Expand Down Expand Up @@ -110,20 +111,21 @@ def activateEmail(request, user, to_email):


def send_delete_account_confirmation_mail(request, user):
mail_subject = 'Confirm Your Account Deletion Request'
message = render_to_string('profiles/emails/template_delete_account.html', {
context = {
'user': user,
'domain': get_current_site(request).domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': default_token_generator.make_token(user),
'protocol': 'https' if request.is_secure() else 'http'
})
email = EmailMessage(mail_subject, message, to=[user.email])
if email.send():
messages.success(request, f'Dear {user.username}, please go to your email inbox and click on \
the link to complete the deletion process. *Note: Check your spam folder.')
else:
messages.error(request, f'Problem sending confirmation email.')
}
codalab_send_mail(
context_data=context,
subject=f'Confirm Your Account Deletion Request',
html_file="profiles/emails/template_delete_account.html",
text_file="profiles/emails/template_delete_account.txt",
to_email=[user.email]
)
messages.success(request, f'Dear {user.username}, please go to your email inbox and click on the link to complete the deletion process. *Note: Check your spam folder.')


def send_user_deletion_notice_to_admin(user):
Expand All @@ -140,8 +142,7 @@ def send_user_deletion_notice_to_admin(user):
queues = user.queues.all()
posts = Post.objects.filter(posted_by=user)

mail_subject = f'Notice: user {user.username} removed his account'
message = render_to_string('profiles/emails/template_delete_account_notice.html', {
context = {
ihsaan-ullah marked this conversation as resolved.
Show resolved Hide resolved
'user': user,
'organizations': organizations,
'competitions_organizer': competitions_organizer,
Expand All @@ -152,16 +153,24 @@ def send_user_deletion_notice_to_admin(user):
'tasks': tasks,
'queues': queues,
'posts': posts
})
email = EmailMessage(mail_subject, message, to=admin_emails)
email.send()
}
codalab_send_mail(
context_data=context,
subject=f'Notice: user {user.username} removed his account',
html_file="profiles/emails/template_delete_account_notice.html",
text_file="profiles/emails/template_delete_account_notice.txt",
to_email=admin_emails
)


def send_user_deletion_confirmed(email):
mail_subject = f'Codabench: your account has been successfully removed'
message = render_to_string('profiles/emails/template_delete_account_confirmed.html')
email = EmailMessage(mail_subject, message, to=[email])
email.send()
codalab_send_mail(
context_data={},
subject=f'Codabench: your account has been successfully removed',
html_file="profiles/emails/template_delete_account_confirmed.html",
text_file="profiles/emails/template_delete_account_confirmed.txt",
to_email=[email]
)


def delete(request, uidb64, token):
Expand Down
9 changes: 9 additions & 0 deletions src/static/riot/competitions/detail/participant_manager.tag
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
<option value="denied">Denied</option>
<option value="unknown">Unknown</option>
</select>
<div class="ui checkbox">
<input type="checkbox" ref="participant_show_deleted" onchange="{ update_participants.bind(this, undefined) }">
<label>Show deleted accounts</label>
</div>
<div class="ui blue icon button" onclick="{show_email_modal.bind(this, undefined)}"><i class="envelope icon"></i> Email all participants</div>
<table class="ui celled striped table">
<thead>
Expand Down Expand Up @@ -140,6 +144,11 @@
filters.status = status
}

let show_deleted_users = self.refs.participant_show_deleted.checked
if (show_deleted_users !== null && show_deleted_users === false) {
filters.user__is_deleted = show_deleted_users
}

CODALAB.api.get_participants(filters)
.done(participants => {
self.participants = participants
Expand Down
24 changes: 24 additions & 0 deletions src/templates/profiles/emails/template_delete_account.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends 'emails/base_email.html' %}

{% block content %}
<p>We have received your request to delete your account.</p>
<p>To proceed with the deletion of your account, please confirm your request by clicking the link below:</p>
<p><a href="{{ protocol }}://{{ domain }}{% url 'accounts:delete' uidb64=uid token=token %}">{{ protocol }}://{{ domain }}{% url 'accounts:delete' uidb64=uid token=token %}</a></p>

<br>

<p><strong>Important Information:</strong></p>
<ul>
<li>Once confirmed, all your personal data will be permanently deleted or anonymized, except for competitions and submissions retained under our user agreement.</li>
<li>After deletion, you will no longer be eligible for any cash prizes in ongoing or future competitions.</li>
<li>If you wish to delete any submissions, please do so before confirming your account deletion.</li>
</ul>

<br>

<p>If you did not request this action or have changed your mind, you can safely ignore this email, and your account will remain intact.</p>

<br>

<p>Thank you for being part of our community.</p>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% extends 'emails/base_email.html' %}

{% block content %}
<p>Your account has been successfully removed. Thank you for being part of our community.</p>
<br>
<p>If you change your mind, you can create a new account at any time. We'd be happy to welcome you back!</p>
{% endblock %}
118 changes: 118 additions & 0 deletions src/templates/profiles/emails/template_delete_account_notice.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{% extends 'emails/base_email.html' %}

{% block content %}
<h2>User account deletion notice</h2>

<p>You are receiving this notice because your are an administrator of the platform.</p>

<br>

<p>The following user has removed their account:</p>
<ul>
<li><strong>id:</strong> {{ user.id }}</li>
<li><strong>username:</strong> {{ user.username }}</li>
<li><strong>email:</strong> {{ user.email }}</li>
</ul>

<br>

<h3>Summary</h3>

<ul>
<li>Organizations: {{ organizations|length }}</li>
<li>Competitions owner: {{ competitions_organizer|length }} </li>
<li>Competitions participation: {{ competitions_participation|length }}</li>
<li>Submissions: {{ submissions|length }}</li>
<li>Data: {{ data|length }}</li>
<li>DataGroups: {{ data_groups|length }}</li>
<li>Tasks: {{ tasks|length }}</li>
<li>Queues: {{ queues|length }}</li>
<li>Posts: {{ posts|length }}</li>
</ul>

<h3>Details</h3>

<h4>Organizations the user is part of:</h4>
<ul>
{% for organization in organizations.all %}
<li>
<a class="item" href="{% url 'profiles:organization_profile' pk=organization.id %}">
{{ organization }}
</a>
</li>
{% endfor %}
</ul>

<h4>Competitions the user is the owner:</h4>
<ul>
{% for competition in competitions_organizer.all %}
<li>
<a class="item" href="{% url 'competitions:detail' pk=competition.pk %}">
{{ competition }}
</a>
</li>
{% endfor %}
</ul>

<h4>Competitions the user participated in:</h4>
<ul>
{% for competition in competitions_participation.all %}
<li>
<a class="item" href="{% url 'competitions:detail' pk=competition.pk %}">
{{ competition }}
</a>
</li>
{% endfor %}
</ul>

<h4>Submissions from the user:</h4>
<ul>
{% for submission in submissions.all %}
<li>
{{ submission }}
</li>
{% endfor %}
</ul>

<h4>Data created by the user</h4>
<ul>
{% for d in data.all %}
<li>
{{ d }}
</li>
{% endfor %}
</ul>

<h4>DataGroups created by the user</h4>
<ul>
{% for data_group in data_groups.all %}
<li>{{ data_group }}</li>
{% endfor %}
</ul>

<h4>Tasks created by the user</h4>
<ul>
{% for task in tasks.all %}
<li>
<a class="item" href="{% url 'tasks:detail' pk=task.pk %}">
{{ task }}
</a>
</li>
{% endfor %}
</ul>

<h4>Queues created by the user</h4>
<ul>
{% for queue in queues.all %}
<li>{{ queue }}</li>
{% endfor %}
</ul>

<h4>Posts posted by the user</h4>
<ul>
{% for post in posts.all %}
<li>{{ post }}</li>
{% endfor %}
</ul>

{% endblock %}