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
11 changes: 11 additions & 0 deletions TWLight/crons.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,14 @@ def do(self):
management.call_command("djmail_delete_old_messages", days=100)
except Exception as e:
capture_exception(e)


class RetrieveMonthlyUsers(CronJobBase):
schedule = Schedule(run_monthly_on_days=1)
code = "users.retrieve_monthly_users"

def do(self):
try:
management.call_command("retrieve_monthly_users")
except Exception as e:
capture_exception(e)
20 changes: 19 additions & 1 deletion TWLight/emails/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
whether to send synchronously or asynchronously based on the value of
settings.DJMAIL_REAL_BACKEND.
"""

from djmail import template_mail
from djmail.template_mail import MagicMailBuilder, InlineCSSTemplateMail
import logging
import os
from reversion.models import Version

from django_comments.models import Comment
Expand All @@ -38,7 +40,7 @@
from TWLight.applications.signals import Reminder
from TWLight.resources.models import AccessCode, Partner
from TWLight.users.groups import get_restricted
from TWLight.users.signals import Notice
from TWLight.users.signals import Notice, UserLoginRetrieval


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -80,6 +82,10 @@ class UserRenewalNotice(template_mail.TemplateMail):
name = "user_renewal_notice"


class UserRetrieveMonthlyLogins(template_mail.TemplateMail):
name = "user_retrieve_monthly_logins"


@receiver(Reminder.coordinator_reminder)
def send_coordinator_reminder_emails(sender, **kwargs):
"""
Expand Down Expand Up @@ -492,3 +498,15 @@ def notify_applicants_when_waitlisted(sender, instance, **kwargs):
status__in=[Application.PENDING, Application.QUESTION]
):
send_waitlist_notification_email(app)


@receiver(UserLoginRetrieval.user_retrieve_monthly_logins)
def send_user_login_retrieval_email(sender, **kwargs):
monthly_users = kwargs["monthly_users"]
email = UserRetrieveMonthlyLogins()
logger.info("Email constructed.")
email.send(
os.environ.get("TWLIGHT_ERROR_MAILTO", "wikipedialibrary@wikimedia.org"),
{"monthly_users": monthly_users},
)
logger.info("Email queued.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<body>
<p> Hi TWL team, </p>
<p> Here is a list of the users that logged-in last month that have approved
applications and current authorizations. </p>

<p> {{ monthly_users|json_script:"monthly-users" }} </p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Hi TWL team,
Here is a list of the users that logged-in last month that have approved applications and current authorizations.
{{ monthly_users|json_script:"monthly-users" }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Monthly user report
1 change: 1 addition & 0 deletions TWLight/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def get_django_faker_languages_intersection(languages):
"TWLight.crons.UserUpdateEligibilityCronJob",
"TWLight.crons.ClearSessions",
"TWLight.crons.DeleteOldEmails",
"TWLight.crons.RetrieveMonthlyUsers",
]
# We will only be keeping 100 days' worth of cron logs
DJANGO_CRON_DELETE_LOGS_OLDER_THAN = 100
Expand Down
55 changes: 55 additions & 0 deletions TWLight/users/management/commands/retrieve_monthly_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import calendar
import datetime
from dateutil.relativedelta import relativedelta

from django.core.management.base import BaseCommand
from django.db import connection

from TWLight.users.signals import UserLoginRetrieval


class Command(BaseCommand):
help = "Retrieves user names that have logged-in in the past month and have approved applications and current authorizations."

def handle(self, *args, **options):
current_date = datetime.datetime.now(datetime.timezone.utc).date()
last_month = current_date - relativedelta(months=1)
first_day_last_month = datetime.date(last_month.year, last_month.month, 1)
_, last_day = calendar.monthrange(last_month.year, last_month.month)
last_day_last_month = datetime.date(last_month.year, last_month.month, last_day)

raw_query = """SELECT users_editor.wp_username, IF(
-- has application status APPROVED = 2 SENT = 4
(applications_application.status = 2 OR applications_application.status = 4), 'true', 'false') AS has_approved_apps,
-- has authorizations that were:
IF((
-- created no more than a year ago or
users_authorization.date_authorized >= date_sub(now(),interval 1 year) OR
-- expired no more than a year ago or
users_authorization.date_expires >= date_sub(now(),interval 1 year) OR
-- are currently active (eg. have currently associated partners)
COUNT(users_authorization_partners.id) > 0
), 'true', 'false') AS has_current_auths
FROM auth_user JOIN users_editor ON auth_user.id = users_editor.user_id
-- left outer join used to grab apps for approved_apps virtual column
LEFT OUTER JOIN applications_application ON users_editor.id = applications_application.editor_id
-- left outer join used to grab auths for current_auths virtual column
LEFT OUTER JOIN users_authorization ON auth_user.id = users_authorization.user_id
-- left outer join used to grab auth partners for current_auths virtual column
LEFT OUTER JOIN users_authorization_partners ON users_authorization.id = users_authorization_partners.authorization_id
-- limit to people who logged in within the last month
WHERE auth_user.last_login >= '{first_day_last_month}' AND auth_user.last_login <= '{last_day_last_month}'
GROUP BY users_editor.wp_username;""".format(
first_day_last_month=first_day_last_month,
last_day_last_month=last_day_last_month,
)

with connection.cursor() as cursor:
cursor.execute(raw_query)
columns = [col[0] for col in cursor.description]
monthly_users = [dict(zip(columns, row)) for row in cursor.fetchall()]

if monthly_users:
UserLoginRetrieval.user_retrieve_monthly_logins.send(
sender=self.__class__, monthly_users=monthly_users
)
4 changes: 4 additions & 0 deletions TWLight/users/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class Notice(object):
user_renewal_notice = Signal()


class UserLoginRetrieval(object):
user_retrieve_monthly_logins = Signal()


@receiver(post_save, sender=User)
def clear_inactive_user_sessions(sender, instance, **kwargs):
"""Clear sessions after user is marked as inactive."""
Expand Down