diff --git a/TWLight/emails/management/commands/cleanup_email.py b/TWLight/emails/management/commands/cleanup_email.py deleted file mode 100644 index bed748b4c7..0000000000 --- a/TWLight/emails/management/commands/cleanup_email.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from django.core.management.base import BaseCommand - -from TWLight.emails.models import Message - - -class Command(BaseCommand): - help = "Delete unsent emails." - - def add_arguments(self, parser): - parser.add_argument( - "--subject", - type=str, - required=False, - help="Email subject", - ) - - def handle(self, *args, **options): - subject = options["subject"] - - if subject is None: - Message.twl.unsent().delete() - else: - Message.twl.filter(subject=subject).unsent().delete() diff --git a/TWLight/emails/models.py b/TWLight/emails/models.py deleted file mode 100644 index 262e9e1a6a..0000000000 --- a/TWLight/emails/models.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf import settings -from django.contrib.auth.models import User -from django.db import models -from django.utils.translation import gettext as _, override - -from djmail.models import Message - -from TWLight.users.models import UserProfile - - -class MessageQuerySet(models.QuerySet): - def unsent(self): - return self.exclude(status=Message.STATUS_SENT) - - def users_with_unsent(self): - email_addresses = self.unsent().values_list("to_email", flat=True) - return User.objects.filter(email__in=email_addresses) - - def userprofiles_with_unsent(self): - email_addresses = self.unsent().values_list("to_email", flat=True) - return UserProfile.objects.select_related("user").filter( - user__email__in=email_addresses - ) - - def user_pks_with_subject_list(self, subject, users): - if users is None: - return [] - - subjects = [] - # Get the localized subject for each available language - for lang_code, _lang_name in settings.LANGUAGES: - try: - with override(lang_code): - # Translators: do not translate - subjects.append(_(subject)) - except ValueError: - pass - - # Search for repients of existing messages with the one of the localized email subjects. - previous_recipients = self.filter( - subject__in=subjects, - ).values_list("to_email", flat=True) - - # return a list of pks for users with matching email addresses - return users.filter(email__in=previous_recipients).values_list("pk", flat=True) - - -class MessageManager(models.Manager): - def get_queryset(self): - return MessageQuerySet(self.model, using=self._db) - - def unsent(self): - return self.get_queryset().unsent() - - def userprofiles_with_unsent(self): - return self.get_queryset().userprofiles_with_unsent() - - def user_pks_with_subject_list(self, subject, users): - return self.get_queryset().user_pks_with_subject_list( - subject=subject, users=users - ) - - -# add "twl" manager to Message -Message.add_to_class("twl", MessageManager()) diff --git a/TWLight/emails/tasks.py b/TWLight/emails/tasks.py index ee0a455824..65fbdc4c81 100644 --- a/TWLight/emails/tasks.py +++ b/TWLight/emails/tasks.py @@ -85,10 +85,6 @@ class RejectionNotification(template_mail.TemplateMail): name = "rejection_notification" -class SurveyActiveUser(template_mail.TemplateMail): - name = "survey_active_user" - - class Test(template_mail.TemplateMail): name = "test" @@ -207,41 +203,6 @@ def send_user_renewal_notice_emails(sender, **kwargs): return bool(result) -def send_survey_active_user_email(connection=email_connection(), **kwargs): - """ - Any time the related managment command is run, this sends a survey - invitation to qualifying editors. - """ - user_email = kwargs["user_email"] - user_lang = kwargs["user_lang"] - survey_id = kwargs["survey_id"] - survey_langs = kwargs["survey_langs"] - - # Default survey language is english - survey_lang = "en" - - # Set survey language to user language if available - if user_lang in survey_langs: - survey_lang = user_lang - - base_url = "https://wikimediafoundation.limesurvey.net/" - link = "{base}{id}?lang={lang}".format( - base=base_url, id=survey_id, lang=survey_lang - ) - - template_email = SurveyActiveUser() - - email = template_email.make_email_object( - user_email, - { - "lang": user_lang, - "link": link, - }, - ) - message = Message.from_email_message(email) - _safe_send_message(message, connection=connection) - - def send_test(connection=email_connection(), **kwargs): user_email = kwargs["email"] template_email = Test() diff --git a/TWLight/emails/templates/emails/survey_active_user-body-html.html b/TWLight/emails/templates/emails/survey_active_user-body-html.html deleted file mode 100644 index 76853ab0b6..0000000000 --- a/TWLight/emails/templates/emails/survey_active_user-body-html.html +++ /dev/null @@ -1,23 +0,0 @@ -{% load i18n %} - - -{% comment %}Translators: This email is sent to users when we invite them to respond to a survey. Don't translate Jinja variables in curly braces like {{ link }}. Translate Wikipedia Library in the same way as the global branch is named (click through from https://meta.wikimedia.org/wiki/The_Wikipedia_Library).{% endcomment %} -{% blocktranslate trimmed %} -

Hi,

-

We are currently running a survey to understand how editors use -The Wikipedia Library. According to our records, you have logged in to -The Wikipedia Library at least once, so we would love to hear from you!

-

{{ link }}

-

We really appreciate your feedback - this survey will help us understand how -The Wikipedia Library is currently being used, and where it could use -improvement. Your responses will directly impact how the Wikimedia Foundation -prioritises technical and partnership work for this project.

-

The survey should take 5-10 minutes to complete.

-

If you would like to opt-out of being contacted with future Wikipedia Library -surveys, please let us know by emailing wikipedialibrary@wikimedia.org and we -will remove you from any future mailing.

-

Thanks,

-

The Wikipedia Library team

-{% endblocktranslate %} - - diff --git a/TWLight/emails/templates/emails/survey_active_user-body-text.html b/TWLight/emails/templates/emails/survey_active_user-body-text.html deleted file mode 100644 index 1a0b383bfe..0000000000 --- a/TWLight/emails/templates/emails/survey_active_user-body-text.html +++ /dev/null @@ -1,26 +0,0 @@ -{% load i18n %} -{% comment %}Translators: This email is sent to users when we invite them to respond to a survey. Don't translate Jinja variables in curly braces like {{ link }}. Translate Wikipedia Library in the same way as the global branch is named (click through from https://meta.wikimedia.org/wiki/The_Wikipedia_Library).{% endcomment %} -{% blocktranslate trimmed %} -Hi, - -We are currently running a survey to understand how editors use -The Wikipedia Library. According to our records, you have logged in to -The Wikipedia Library at least once, so we would love to hear from you! - -{{ link }} - -We really appreciate your feedback - this survey will help us understand how -The Wikipedia Library is currently being used, and where it could use -improvement. Your responses will directly impact how the Wikimedia Foundation -prioritises technical and partnership work for this project. - -The survey should take 5-10 minutes to complete. - -If you would like to opt-out of being contacted with future Wikipedia Library -surveys, please let us know by emailing wikipedialibrary@wikimedia.org and we -will remove you from any future mailing. - -Thanks, - -The Wikipedia Library team -{% endblocktranslate %} diff --git a/TWLight/emails/templates/emails/survey_active_user-subject.html b/TWLight/emails/templates/emails/survey_active_user-subject.html deleted file mode 100644 index 847dd6783f..0000000000 --- a/TWLight/emails/templates/emails/survey_active_user-subject.html +++ /dev/null @@ -1,4 +0,0 @@ -{% load i18n %} - -{% comment %}Translators: This is the subject of an email sent to users when we invite them to respond to a survey. Translate Wikipedia Library in the same way as the global branch is named (click through from https://meta.wikimedia.org/wiki/The_Wikipedia_Library).{% endcomment %} -{% trans 'The Wikipedia Library needs your help!' %} diff --git a/TWLight/emails/templates/emails/test-body-html.html b/TWLight/emails/templates/emails/test-body-html.html deleted file mode 100644 index 27da8f6c1a..0000000000 --- a/TWLight/emails/templates/emails/test-body-html.html +++ /dev/null @@ -1 +0,0 @@ -

Please disregard this message; this is only a test

diff --git a/TWLight/emails/templates/emails/test-body-text.html b/TWLight/emails/templates/emails/test-body-text.html deleted file mode 100644 index c76a832c15..0000000000 --- a/TWLight/emails/templates/emails/test-body-text.html +++ /dev/null @@ -1 +0,0 @@ -Please disregard this message; this is only a test diff --git a/TWLight/emails/templates/emails/test-subject.html b/TWLight/emails/templates/emails/test-subject.html deleted file mode 100644 index 853a60e1c9..0000000000 --- a/TWLight/emails/templates/emails/test-subject.html +++ /dev/null @@ -1 +0,0 @@ -test message; please disregard diff --git a/TWLight/emails/tests.py b/TWLight/emails/tests.py index 0f0acec093..85561280d1 100644 --- a/TWLight/emails/tests.py +++ b/TWLight/emails/tests.py @@ -35,7 +35,6 @@ send_approval_notification_email, send_rejection_notification_email, send_user_renewal_notice_emails, - send_survey_active_user_email, ) @@ -827,152 +826,3 @@ def test_send_coordinator_reminder_email(self): self.assertIn("1 pending application", mail.outbox[0].body) self.assertIn("1 under discussion application", mail.outbox[0].body) self.assertIn("1 approved application", mail.outbox[0].body) - - -class SurveyActiveUsersEmailTest(TestCase): - @classmethod - def setUpTestData(cls): - """ - Creates a survey-eligible user, several ineligible users, and one already sent message. - Returns - ------- - None - """ - - now = timezone.now() - - eligible = EditorFactory(user__email="editor@example.com") - eligible.wp_not_blocked = True - eligible.wp_bundle_eligible = True - eligible.wp_account_old_enough = True - eligible.wp_registered = now - timedelta(days=182) - eligible.wp_enough_edits = True - eligible.user.userprofile.terms_of_use = True - eligible.user.userprofile.save() - eligible.user.last_login = now - eligible.user.save() - eligible.save() - cls.eligible = eligible.user - - blocked = EditorFactory(user__email="blocked@example.com") - blocked.wp_not_blocked = False - blocked.wp_bundle_eligible = True - blocked.wp_account_old_enough = True - blocked.wp_registered = now - timedelta(days=182) - blocked.wp_enough_edits = True - blocked.user.userprofile.terms_of_use = True - blocked.user.userprofile.save() - blocked.user.last_login = now - blocked.user.save() - blocked.save() - - already_sent = EditorFactory(user__email="alreadysent@example.com") - already_sent.wp_not_blocked = True - already_sent.wp_bundle_eligible = True - already_sent.wp_account_old_enough = True - already_sent.wp_registered = now - timedelta(days=182) - already_sent.wp_enough_edits = True - already_sent.user.userprofile.terms_of_use = True - already_sent.user.userprofile.save() - already_sent.user.last_login = now - already_sent.user.save() - already_sent.save() - - wmf_email = EditorFactory(user__email="editor@wikimedia.org") - wmf_email.wp_not_blocked = True - wmf_email.wp_bundle_eligible = True - wmf_email.wp_account_old_enough = True - wmf_email.wp_registered = now - timedelta(days=182) - wmf_email.wp_enough_edits = True - wmf_email.user.userprofile.terms_of_use = True - wmf_email.user.userprofile.save() - wmf_email.user.last_login = now - wmf_email.user.save() - wmf_email.save() - - too_new_at_login = EditorFactory(user__email="toonew@example.com") - too_new_at_login.wp_not_blocked = True - too_new_at_login.wp_bundle_eligible = True - too_new_at_login.wp_account_old_enough = True - too_new_at_login.wp_registered = now - timedelta(days=182) - too_new_at_login.wp_enough_edits = True - too_new_at_login.user.userprofile.terms_of_use = True - too_new_at_login.user.userprofile.save() - too_new_at_login.user.last_login = now - timedelta(days=30) - too_new_at_login.user.save() - too_new_at_login.save() - - not_enough_edits = EditorFactory(user__email="notenoughedits@example.com") - not_enough_edits.wp_not_blocked = True - not_enough_edits.wp_bundle_eligible = True - not_enough_edits.wp_account_old_enough = True - not_enough_edits.wp_registered = now - timedelta(days=182) - not_enough_edits.wp_enough_edits = False - not_enough_edits.user.userprofile.terms_of_use = True - not_enough_edits.user.userprofile.save() - not_enough_edits.user.last_login = now - not_enough_edits.user.save() - not_enough_edits.save() - - inactive = EditorFactory(user__email="inactive@example.com") - inactive.wp_not_blocked = True - inactive.wp_bundle_eligible = True - inactive.wp_account_old_enough = True - inactive.wp_registered = now - timedelta(days=182) - inactive.wp_enough_edits = True - inactive.user.userprofile.terms_of_use = True - inactive.user.userprofile.save() - inactive.user.last_login = now - inactive.user.is_active = False - inactive.user.save() - inactive.save() - - staff = EditorFactory(user__email="staff@example.com") - staff.wp_not_blocked = True - staff.wp_bundle_eligible = True - staff.wp_account_old_enough = True - staff.wp_registered = now - timedelta(days=182) - staff.wp_enough_edits = True - staff.user.userprofile.terms_of_use = True - staff.user.userprofile.save() - staff.user.last_login = now - staff.user.is_staff = True - staff.user.save() - staff.save() - - superuser = EditorFactory(user__email="superuser@example.com") - superuser.wp_not_blocked = True - superuser.wp_bundle_eligible = True - superuser.wp_account_old_enough = True - superuser.wp_registered = now - timedelta(days=182) - superuser.wp_enough_edits = True - superuser.user.userprofile.terms_of_use = True - superuser.user.userprofile.save() - superuser.user.last_login = now - superuser.user.is_superuser = True - superuser.user.save() - superuser.save() - - # Use the same override as djmail itself since the command dynamically changes the backend - @override_settings( - EMAIL_BACKEND="djmail.backends.default.EmailBackend", - DJMAIL_REAL_BACKEND="django.core.mail.backends.locmem.EmailBackend", - ) - def test_survey_active_users_command(self): - # pre-send an email to the "alreadysent" editor - already_sent_msg = mail.EmailMessage( - "The Wikipedia Library needs your help!", - "Body", - "sender@example.com", - ["alreadysent@example.com"], - ) - already_sent_msg.send() - - call_command( - "survey_active_users", - "000001", - "en", - backend="djmail.backends.default.EmailBackend", - ) - self.assertEqual(len(mail.outbox), 2) - self.assertEqual(mail.outbox[1].to, [self.eligible.email]) diff --git a/TWLight/users/management/commands/survey_active_users.py b/TWLight/users/management/commands/survey_active_users.py deleted file mode 100644 index 43516cc914..0000000000 --- a/TWLight/users/management/commands/survey_active_users.py +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: utf-8 -*- -import logging - -from django.conf import settings -from django.contrib.auth.models import User -from django.core.management.base import BaseCommand, CommandError -from django.db.models import DurationField, ExpressionWrapper, F, Q -from django.db.models.functions import TruncDate -from django.utils.timezone import timedelta -from django.utils.translation import gettext_lazy as _ - -from TWLight.emails.models import Message -from TWLight.emails.tasks import email_connection, send_survey_active_user_email -from TWLight.users.groups import get_restricted - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - help = "Sends survey invitation email to active users." - - def add_arguments(self, parser): - parser.add_argument( - "survey_id", type=int, help="ID number for corresponding survey" - ) - parser.add_argument( - "lang", - nargs="+", - type=str, - help="List of localized language codes for this survey", - ) - parser.add_argument( - "--staff_test", - action="store_true", - required=False, - help="Email only staff users who qualify other than staff status", - ) - parser.add_argument( - "--batch_size", - type=int, - required=False, - help="number of emails to send; default is 1000", - ) - parser.add_argument( - "--backend", - type=str, - required=False, - help="djmail backend to use; default is TWLight.emails.backends.mediawiki.EmailBackend", - ) - - def handle(self, *args, **options): - # Validate the lang args - survey_langs = options["lang"] - valid_langs = [] - invalid_langs = [] - for lang_code, _lang_name in settings.LANGUAGES: - valid_langs.append(lang_code) - - for survey_lang in survey_langs: - if survey_lang not in valid_langs: - invalid_langs.append(survey_lang) - - if invalid_langs: - raise CommandError( - "invalid lang argument in list: {}".format(invalid_langs) - ) - - # default mode excludes users who are staff or superusers - role_filter = Q(is_staff=False) & Q(is_superuser=False) - - batch_size = options["batch_size"] if options["batch_size"] else 1000 - - # use mediawiki email api by default - backend = ( - options["backend"] - if options["backend"] - else "TWLight.emails.backends.mediawiki.EmailBackend" - ) - - # test mode excludes users who are not staff and ignores superuser status - if options["staff_test"]: - role_filter = Q(is_staff=True) - - # All Wikipedia Library users who: - users = ( - User.objects.select_related("editor", "userprofile") - .annotate( - # calculate account age at last login - last_login_age=ExpressionWrapper( - TruncDate(F("last_login")) - F("editor__wp_registered"), - output_field=DurationField(), - ) - ) - .filter( - # meet the mode-dependent role requirements - role_filter, - # have not restricted data processing - ~Q(groups__name__in=[get_restricted()]), - # meet the block criterion or are exempt - Q(editor__wp_not_blocked=True) | Q(editor__ignore_wp_blocks=True), - # have an non-wikimedia.org email address - Q(email__isnull=False) - & ~Q(email="") - & ~Q(email__endswith="@wikimedia.org"), - # meet the 6 month criterion as of last login or are exempt - Q(last_login_age__gte=timedelta(days=182)) - | Q(editor__ignore_wp_account_age_requirement=True), - # meet the 500 edit criterion or are exempt - Q(editor__wp_enough_edits=True) - | Q(editor__ignore_wp_edit_requirement=True), - # are 'active' - is_active=True, - ) - ) - logger.info("{} users qualify".format(users.count())) - previously_sent_user_pks = Message.twl.user_pks_with_subject_list( - # Translators: email subject line - subject=_("The Wikipedia Library needs your help!"), - users=users, - ) - logger.info( - "{} users previously sent message will be skipped".format( - len(previously_sent_user_pks) - ) - ) - users = ( - users.exclude(pk__in=previously_sent_user_pks) - .distinct() - .order_by("last_login") - ) - logger.info("{} remaining users qualify".format(users.count())) - users = users[:batch_size] - logger.info("attempting to send to {} users".format(users.count())) - - # Use a single connection to send all emails - connection = email_connection(backend=backend) - connection.open() - - # send the emails - for user in users: - try: - send_survey_active_user_email( - sender=self.__class__, - connection=connection, # passing in the connection lets us handle these in bulk - user_email=user.email, - user_lang=user.userprofile.lang, - survey_id=options["survey_id"], - survey_langs=survey_langs, - ) - except Exception as e: - logger.error(e) - - connection.close() diff --git a/TWLight/users/models.py b/TWLight/users/models.py index bfe78ea0c2..e9372d9d53 100644 --- a/TWLight/users/models.py +++ b/TWLight/users/models.py @@ -195,10 +195,6 @@ class Meta: blank=True, help_text="The partner(s) that the user has marked as favorite.", ) - # @TODO: drop this field - survey_email_sent = models.BooleanField( - default=False, help_text="Has this user recieved the most recent survey email?" - ) def delete_my_library_cache(self): """