Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 4 additions & 5 deletions optimap/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,13 @@
Q_CLUSTER = {
'name': 'optimap',
'workers': 1,
'timeout': 10,
'retry': 20,
'timeout': 60 * 10, # seconds, must be less than retry
'retry': 60 * 11,
'save_limit': 0, # unlimited
'queue_limit': 50,
'bulk': 10,
'orm': 'default',
'ack_failures': True,
'max_attempts': 5,
'attempt_count': 0,
'max_attempts': 5
}

CACHES = {
Expand Down
21 changes: 21 additions & 0 deletions publications/migrations/0003_remove_customuser_deleted_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.1.9 on 2025-05-21 13:35

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('publications', '0002_source_collection_name_source_tags'),
]

operations = [
migrations.RemoveField(
model_name='customuser',
name='deleted',
),
migrations.RemoveField(
model_name='customuser',
name='deleted_at',
),
]
17 changes: 0 additions & 17 deletions publications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,9 @@
)

class CustomUser(AbstractUser):
deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
groups = models.ManyToManyField(Group, related_name="publications_users", blank=True)
user_permissions = models.ManyToManyField(Permission, related_name="publications_users_permissions", blank=True)

def soft_delete(self):
"""Marks the user as deleted instead of removing from the database."""
self.deleted = True
self.deleted_at = now()
self.save()
logger.info(f"User {self.username} (ID: {self.id}) was soft deleted at {self.deleted_at}")


def restore(self):
"""Restores a previously deleted user."""
self.deleted = False
self.deleted_at = None
self.save()
logger.info(f"User {self.username} (ID: {self.id}) was restored.")

class Publication(models.Model):
# required fields
title = models.TextField()
Expand Down
6 changes: 0 additions & 6 deletions publications/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,3 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email"]

def to_representation(self, instance):
"""Ensure deleted users are excluded from serialization."""
if instance.deleted:
return None
return super().to_representation(instance)
2 changes: 1 addition & 1 deletion publications/templates/imprint.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ <h2>Contact</h2>
Professur für Geoinformatik - KOMET project team</br>
Helmholtzstr. 10</br>
D-01069 Dresden</br>
Email: [email protected]
Email: <a href="mailto:[email protected]">[email protected]</a>
</p>
<h2>Responsible for the content</h2>
<p>Dr. Daniel Nüst</br>
Expand Down
11 changes: 4 additions & 7 deletions publications/templates/menu_snippet.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
id="navbarDarkDropdown1"
role="button"
data-toggle="dropdown"
aria-expanded="false"
>
aria-expanded="false">
<i class="fa-solid fa-circle-user fa-3x"></i>
</a>
<ul
class="dropdown-menu dropdown-menu-right"
aria-labelledby="navbarDarkDropdown1"
>
<span class="dropdown-item-text"
>New around here? Please login to create a new account.</span
>
aria-labelledby="navbarDarkDropdown1">
<span class="dropdown-item-text">New around here? Please login to create a new account.</span>
<span class="dropdown-item-text">Want to stay anonymous? Use a public inbox like <a href="https://www.mailinator.com/">Mailinator</a> or check out our <a href="{% url 'optimap:privacy' %}">privacy information</a>.</span>
<div class="dropdown-divider"></div>
<li class="px-3 py-2">
<form class="form" method="POST" action="{% url 'optimap:loginres' %}">
Expand Down
11 changes: 8 additions & 3 deletions publications/templates/privacy.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,30 @@ <h1 class="py-2">Privacy policy</h1>
<p class="lead">OPTIMAP does not collect or log your personal data. We only store your email to identify your user
account and minimal metadata, such as the date of registration and the last login, to identify and handle stale accounts.</p>

<p>The address of the website is {{ site | urlize }}.</p>
<p>You can use OPTIMAP fully anonymously by using a public inbox, such as <a href="https://www.mailinator.com/" title="Link to Mailinator">Mailinator</a>.</p>

<p>This website does not use any cookies to track users, with the exception of the login status, to which users
<p>This website does not use any cookies to track users or any third-party tracking services (such as Google Analytics or similar), with the exception of the login status, to which users
provide implicit concent by opening an account on the website.</p>

<p>This website utilizes map services provided by the OpenStreetMap Foundation, St John's Innovation Centre, Cowley
Road, Cambridge, CB4 0WS, United Kingdom (short OSMF). Your Internet browser or application will connect to
servers operated by the OSMF located in the United Kingdom and in other countries. The operator of this site has
servers operated by the OSMF located in the United login and in other countries. The operator of this site has
no control over such connections and processing of your data by the OSMF. You can find more information on the
processing of user data by the OSMF in the <a href="https://wiki.osmfoundation.org/wiki/Privacy_Policy">OSMF
privacy policy</a>.
This website integrates the OSMF services exclusively for the legitimate interest (cf. article 6.1f of the GDPR)
of displaying the map functions to the users of the website for interactive exploration of the presented datasets.
</p>

<p>You can <strong>delete</strong> your account yourself after logging in (Settings > Delete account), which will deactivate all notifications and feeds.
Stale accounts that have not been used in over a year are automatically deleted after a final warning email.</p>

<h1 class="py-2">Notice of liability</h1>
<p>Although we check the content carefully, we cannot accept responsibility for the content of external links. The
linked sites' carriers are responsible for their sites' content.</p>

<p>The address of the website is {{ site | urlize }}.</p>

{% include "imprint.html" %}
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions publications/templates/user_settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ <h5 class="modal-title">Delete Account</h5>
</button>
</div>
<div class="modal-body">
<p>Do you really want to delete this account ?</p>
<p class="text-wrap text-break">Do you really want to delete this account? A confirmation email is sent to you after clicking the Delete button below.</p>
</div>
<div class="modal-footer">
<form
Expand All @@ -200,15 +200,15 @@ <h5 class="modal-title">Delete Account</h5>
{% csrf_token %}
<button
type="submit"
class="btn btn-primary"
class="btn btn-outline-danger"
name="dltbutton"
>
Delete
</button>
</form>
<button
type="button"
class="btn btn-secondary"
class="btn btn-primary"
data-dismiss="modal"
>
Close
Expand Down
2 changes: 2 additions & 0 deletions publications/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
path('feed/', RedirectView.as_view(pattern_name='optimap:georss_feed', permanent=True)),
path("loginres/", views.loginres, name="loginres"),
path("privacy/", views.privacy, name="privacy"),
path("contact/", RedirectView.as_view(pattern_name='optimap:privacy', permanent=True)),
path("imprint/", RedirectView.as_view(pattern_name='optimap:privacy', permanent=True)),
path("loginconfirm/", views.Confirmationlogin, name="loginconfirm"),
path("login/<str:token>", views.authenticate_via_magic_link, name="magic_link"),
path("logout/", views.customlogout, name="logout"),
Expand Down
16 changes: 2 additions & 14 deletions publications/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,7 @@ def authenticate_via_magic_link(request, token):
})
user = User.objects.filter(email=email).first()
if user:
if user.deleted:
user.deleted = False
user.deleted_at = None
user.is_active = True
user.save()
is_new = False
else:
is_new = False
is_new = False
else:
user = User.objects.create_user(username=email, email=email)
is_new = True
Expand Down Expand Up @@ -503,13 +496,8 @@ def finalize_account_deletion(request):
messages.error(request, "You are not authorized to delete this account.")
return redirect(reverse('optimap:main'))
user = get_object_or_404(User, id=user_id)
if user.deleted:
messages.warning(request, "This account has already been deleted.")
return redirect(reverse('optimap:usersettings'))
try:
user.deleted = True
user.deleted_at = now()
user.save()
user.delete()
logout(request)
messages.success(request, "Your account has been successfully deleted.")
return redirect(reverse('optimap:main'))
Expand Down
4 changes: 2 additions & 2 deletions tests-ui/test_accountdeletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def test_delete_account(self):
click("Permanently Delete Account")
sleep(3)

self.user.refresh_from_db()
assert self.user.deleted, "User deletion flag was not updated in the database!"
user = User.objects.filter(email=self.email).first()
self.assertIsNone(user, "User was not deleted from the database!")

def tearDown(self):
"""Close browser after test"""
Expand Down
10 changes: 3 additions & 7 deletions tests/test_account_deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,16 @@ def test_finalize_delete_account(self):
"""Test that a user can finalize account deletion"""
session = self.client.session
session["user_delete_token"] = self.delete_token
session.save()
session.save()

# Send delete request
response = self.client.post(reverse("optimap:finalize_delete"))

self.assertEqual(response.status_code, 302)

# Fetch user from DB again
# Try to fetch user from DB again
user = User.objects.filter(id=self.user.id).first()

if user:
self.assertTrue(user.deleted)
else:
self.assertIsNone(user)
self.assertIsNone(user)

def test_invalid_token(self):
"""Test invalid or expired deletion token"""
Expand Down
1 change: 0 additions & 1 deletion tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ def test_login(self):

self.assertFalse(user.is_staff)
self.assertFalse(user.is_superuser)
self.assertFalse(user.deleted)

# check the default fields which we do not want to use are emptly
self.assertEqual(user.first_name, "", "first_name of user must not be set")
Expand Down