Skip to content
Open
4 changes: 3 additions & 1 deletion app/eventyay/base/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,9 @@ class urls(EventUrls):
reset = '{base}reset'
submit = '{base}submit/'
user = '{base}me/'
user_delete = '{base}me/delete'
# TODO: Disabled user_delete URL — delete endpoint is incomplete/broken.
# Will restore once proper deletion logic is ready.
# user_delete = '{base}me/delete'
user_submissions = '{user}submissions/'
user_mails = '{user}mails/'
schedule = '{base}schedule/'
Expand Down
161 changes: 69 additions & 92 deletions app/eventyay/cfp/templates/cfp/event/user_profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,106 +7,83 @@
{% block title %}{% translate "Your Profile" %} :: {% endblock title %}

{% block cfp_header %}
{% include "cfp/includes/forms_header.html" %}
{% compress js %}
<script defer src="{% static 'vendored/zxcvbn.js' %}"></script>
<script defer src="{% static 'common/js/password_strength.js' %}"></script>
{% endcompress %}
{% include "cfp/includes/forms_header.html" %}
{% compress js %}
<script defer src="{% static 'vendored/zxcvbn.js' %}"></script>
<script defer src="{% static 'common/js/password_strength.js' %}"></script>
{% endcompress %}
{% endblock cfp_header %}

{% block content %}
{% html_signal "eventyay.cfp.signals.html_above_profile_page" sender=request.event request=request %}
{% html_signal "eventyay.cfp.signals.html_above_profile_page" sender=request.event request=request %}

<h2>{% translate "Your Profile" %}</h2>
<p>
{% translate "This data will be displayed publicly if your proposal is accepted. It is also visible to reviewers." %}
</p>
<form method="post" enctype="multipart/form-data" class="speaker-profile-form">
{% csrf_token %}
{% include "common/forms/errors.html" with form=profile_form %}
<h2>{% translate "Your Profile" %}</h2>
<p>
{% translate "This data will be displayed publicly if your proposal is accepted. It is also visible to reviewers."
%}
</p>
<form method="post" enctype="multipart/form-data" class="speaker-profile-form">
{% csrf_token %}
{% include "common/forms/errors.html" with form=profile_form %}

{{ profile_form.name.as_field_group }}
{% if profile_form.biography %}
{{ profile_form.biography.as_field_group }}
{% endif %}
{% if request.event.cfp.request_avatar %}
{% include "common/avatar.html" with user=request.user form=profile_form %}
{% endif %}
{% if profile_form.avatar_source %}{{ profile_form.avatar_source.as_field_group }}{% endif %}
{% if profile_form.avatar_license %}{{ profile_form.avatar_license.as_field_group }}{% endif %}
{% if profile_form.availabilities %}
{% include "common/availabilities.html" %}
{{ profile_form.availabilities.as_field_group }}
{% endif %}
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="profile">
<button type="submit" class="btn btn-block btn-success btn-lg">
{{ phrases.base.save }}
</button>
</div>
{{ profile_form.name.as_field_group }}
{% if profile_form.biography %}
{{ profile_form.biography.as_field_group }}
{% endif %}
{% if request.event.cfp.request_avatar %}
{% include "common/avatar.html" with user=request.user form=profile_form %}
{% endif %}
{% if profile_form.avatar_source %}{{ profile_form.avatar_source.as_field_group }}{% endif %}
{% if profile_form.avatar_license %}{{ profile_form.avatar_license.as_field_group }}{% endif %}
{% if profile_form.availabilities %}
{% include "common/availabilities.html" %}
{{ profile_form.availabilities.as_field_group }}
{% endif %}
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="profile">
<button type="submit" class="btn btn-block btn-success btn-lg">
{{ phrases.base.save }}
</button>
</div>
</form>
</div>
</form>

{% if questions_exist %}
<h2>{% translate "We have some questions" %}</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ questions_form }}
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="questions">
<button type="submit" class="btn btn-block btn-success btn-lg">
{{ phrases.base.save }}
</button>
</div>
</div>
</form>
{% endif %}
{% if questions_exist %}
<h2>{% translate "We have some questions" %}</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ questions_form }}
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="questions">
<button type="submit" class="btn btn-block btn-success btn-lg">
{{ phrases.base.save }}
</button>
</div>
</div>
</form>
{% endif %}

<h2>{% translate "Your Account" %}</h2>
<p>{% translate "You can change your log in data here." %}</p>
<form method="post" class="form password-input-form">
{% csrf_token %}
{{ login_form.media }}
{{ login_form.old_password.as_field_group }}
{{ login_form.email.as_field_group }}
{{ login_form.password.as_field_group }}
{{ login_form.password_repeat.as_field_group }}
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="login">
<button type="submit" class="btn btn-block btn-success btn-lg">
{{ phrases.base.save }}
</button>
</div>
<h2>{% translate "Your Account" %}</h2>
<p>{% translate "You can change your log in data here." %}</p>
<form method="post" class="form password-input-form">
{% csrf_token %}
{{ login_form.media }}
{{ login_form.old_password.as_field_group }}
{{ login_form.email.as_field_group }}
{{ login_form.password.as_field_group }}
{{ login_form.password_repeat.as_field_group }}
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="login">
<button type="submit" class="btn btn-block btn-success btn-lg">
{{ phrases.base.save }}
</button>
</div>
</form>
</div>
</form>

{% html_signal "eventyay.common.signals.profile_bottom_html" sender=request.event user=user %}
{% html_signal "eventyay.common.signals.profile_bottom_html" sender=request.event user=user %}

<div>&nbsp;</div>
<h3>{% translate "Account deletion" %}</h3>
<form action="{{ request.event.urls.user_delete }}" method="post" class="form">
{% csrf_token %}
<div class="alert alert-danger">
<div>
{% translate "You can delete your account here – all names, emails, and other personal information will be overwritten. <strong>This action is irreversible.</strong>" %}
</div>
</div>
<div class="form-group"{% if not "really" in request.GET %} style="visibility:hidden;"{% endif %}>
<input type="checkbox" name="really" id="really">
<label class="form-control-label" for="really">
{% translate "I really do want to delete my account, losing access to my proposals and sessions, and overriding my public and private data." %}
</label>
</div>
<div class="row">
<div class="col-md-4 flip ml-auto">
<input type="hidden" name="form" value="">
<button type="submit" class="btn btn-block btn-danger btn-lg">
{% translate "Delete my account" %}
</button>
</div>
</div>
</form>
{% endblock content %}
{% endblock content %}
18 changes: 13 additions & 5 deletions app/eventyay/cfp/urls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.urls import include, path
from django.views.generic import RedirectView
from django.conf import settings

from .views import auth, event, locale, robots, user, wizard

Expand Down Expand Up @@ -33,11 +34,6 @@
name="invitation.view",
),
path("me/", user.ProfileView.as_view(), name="event.user.view"),
path(
"me/delete",
user.DeleteAccountView.as_view(),
name="event.user.delete",
),
path(
"me/submissions/",
user.SubmissionsListView.as_view(),
Expand Down Expand Up @@ -83,3 +79,15 @@
path("locale/set", locale.LocaleSet.as_view(), name="locale.set"),
path("robots.txt", robots.robots_txt, name="robots.txt"),
]

# Conditionally add account deletion route based on feature flag
# TODO: Current implementation doesn't fully remove user data (not GDPR compliant)
# Will be properly implemented in Account Settings
if getattr(settings, 'ENABLE_ACCOUNT_DELETION', False):
urlpatterns.append(
path(
"me/delete",
user.DeleteAccountView.as_view(),
name="event.user.delete",
)
)
8 changes: 8 additions & 0 deletions app/eventyay/cfp/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,14 @@ def form_valid(self, form):


class DeleteAccountView(LoggedInEventPageMixin, View):
"""
Account deletion view - currently disabled by default.

TODO: Current implementation only calls user.deactivate() and does not fully remove user data (not GDPR compliant).
A complete rework with proper data cleanup will be needed once the Account settings feature is implemented.

This view is only accessible when the ENABLE_ACCOUNT_DELETION feature flag is enabled.
"""
@staticmethod
def post(request, event):
if request.POST.get('really'):
Expand Down
4 changes: 4 additions & 0 deletions app/eventyay/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,10 @@ def instance_name(request):
EVENTYAY_SESSION_TIMEOUT_RELATIVE = 3600 * 3
EVENTYAY_SESSION_TIMEOUT_ABSOLUTE = 3600 * 12

# Feature flags
# TODO: Enable when GDPR-compliant account deletion is implemented
ENABLE_ACCOUNT_DELETION = config.getboolean('features', 'enable_account_deletion', fallback=False)

PDFTK = config.get('tools', 'pdftk', fallback=None)
PRETIX_SESSION_TIMEOUT_RELATIVE = 3600 * 3
PRETIX_SESSION_TIMEOUT_ABSOLUTE = 3600 * 12
Expand Down