Skip to content
Open
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
6 changes: 6 additions & 0 deletions config/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ def setup_periodic_tasks(sender, **kwargs):
app.signature("users.tasks.refresh_users_github_photos"),
)

# Remove unverified users. Executes daily at 2:15 AM.
sender.add_periodic_task(
crontab(hour=2, minute=15),
app.signature("users.tasks.remove_unverified_users"),
)

# Clean up old sandbox documents. Executes weekly on Sundays at 2:00 AM.
sender.add_periodic_task(
crontab(day_of_week="sun", hour=2, minute=0),
Expand Down
28 changes: 28 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [`update_library_version_dependencies`](#update_library_version_dependencies)
- [`release_tasks`](#release_tasks)
- [`refresh_users_github_photos`](#refresh_users_github_photos)
- [`remove_unverified_users`](#remove_unverified_users)

## `boost_setup`

Expand Down Expand Up @@ -356,3 +357,30 @@ Preview which users would be updated:

- Calls the `refresh_users_github_photos()` Celery task which queues photo updates for all users with GitHub usernames
- With `--dry-run`, displays information about which users would be updated without making any changes

## `remove_unverified_users`

**Purpose**: Remove unverified users that are candidates for deletion. This command queues a Celery task that deletes user accounts with unverified email addresses that have been registered for more than 14 days (since November 21, 2025). This helps maintain database hygiene by cleaning up abandoned user registrations.

**Example**

```bash
./manage.py remove_unverified_users
```

**Options**

This command takes no options.

**Process**

- Queues a Celery task (`users.tasks.remove_unverified_users`) for execution
- The task finds users who:
- Have claimed accounts (`claimed=True`)
- Have unverified email addresses (`emailaddress__verified=False`)
- Joined on or after November 21, 2025
- Joined more than 14 days before the current date
- Deletes each matching user account
- Logs the deletion process for auditing purposes

**Note**: This command is also executed automatically via a Celery periodic task that runs daily at 2:15 AM.
5 changes: 5 additions & 0 deletions users/constants.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
from django.utils import timezone

LOGIN_METHOD_SESSION_FIELD_NAME = "boost_login_method"

UNVERIFIED_CLEANUP_DAYS = 14
UNVERIFIED_CLEANUP_BEGIN = timezone.datetime(2025, 11, 21, 0, 0, 0)
15 changes: 15 additions & 0 deletions users/management/commands/remove_unverified_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import djclick as click

from users.tasks import remove_unverified_users


@click.command()
def command():
"""Remove unverified users that are candidates for deletion."""
click.echo("Starting remove_unverified_users task...")

try:
result = remove_unverified_users.delay()
click.secho(f"Task queued with ID: {result.id}", fg="green")
except Exception as e:
click.secho(f"Error: {e}", fg="red")
35 changes: 35 additions & 0 deletions users/tasks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from time import sleep
from datetime import timedelta

import structlog

Expand All @@ -13,6 +14,7 @@

from config.celery import app
from core.githubhelper import GithubAPIClient
from users.constants import UNVERIFIED_CLEANUP_DAYS, UNVERIFIED_CLEANUP_BEGIN

logger = structlog.getLogger(__name__)

Expand Down Expand Up @@ -91,3 +93,36 @@ def send_account_deleted_email(email):
settings.DEFAULT_FROM_EMAIL,
[email],
)


@shared_task
def remove_unverified_users():
"""
Removes users that have accounts with unverified email addresses after some time
"""
logger.info("Starting remove_unverified_users task")

try:
cutoff_date = timezone.now() - timedelta(days=UNVERIFIED_CLEANUP_DAYS)
logger.info(f"Joined after {UNVERIFIED_CLEANUP_BEGIN} and before {cutoff_date}")

unverified_users = User.objects.filter(
claimed=True,
emailaddress__verified=False,
date_joined__gte=UNVERIFIED_CLEANUP_BEGIN,
date_joined__lt=cutoff_date,
).order_by("date_joined")

user_count = unverified_users.count()
logger.info(f"Found {user_count} unverified users for deletion")

if user_count == 0:
return

for user in unverified_users:
logger.info(f"Del user: {user.id=}, {user.email=}, {user.date_joined=}")
user.delete()
logger.info(f"Successfully processed {user_count} unverified users")

except Exception as e:
logger.exception(f"Error occurred processing unverified users for removal: {e}")
Loading