Exemplo n.º 1
0
    def send_approval_queue_reminder(self):
        """
        Send notification to approval authority for clubs awaiting approval.
        """
        now = timezone.now()
        group_name = "Approvers"

        # only send notifications if it is currently a weekday
        if now.isoweekday() not in range(1, 6):
            return False

        # get users in group to send notification to
        group = Group.objects.filter(name=group_name).first()
        if group is None:
            self.stdout.write(
                self.style.WARNING(
                    f"There is no Django admin group named '{group_name}' in the database. "
                    "Cannot send out approval queue notification emails!"))
            return False

        emails = [
            e for e in group.user_set.all().values_list("email", flat=True)
            if e
        ]

        if not emails:
            self.stdout.write(
                self.style.WARNING(
                    f"There are no users or no associated emails in the '{group_name}' group. "
                    "No emails will be sent out."))
            return False

        # get clubs that need approval
        queued_clubs = Club.objects.filter(active=True, approved__isnull=True)
        if queued_clubs.exists():
            context = {
                "num_clubs":
                queued_clubs.count(),
                "url":
                f"https://{settings.DOMAIN}/admin#queue",
                "clubs":
                list(
                    queued_clubs.order_by("name").values_list("name",
                                                              flat=True)),
            }
            count = queued_clubs.count()
            send_mail_helper(
                "approval_queue_reminder",
                "{} clubs awaiting review on {}".format(
                    count, settings.BRANDING_SITE_NAME),
                emails,
                context,
            )
            self.stdout.write(
                self.style.SUCCESS(
                    f"Sent approval queue reminder for {count} clubs to {', '.join(emails)}"
                ))

        return True
Exemplo n.º 2
0
def send_hap_intro_email(email, resources, recipient_string, template="intro"):
    """
    Send the Hub@Penn introduction email given the email and the list of resources.
    """

    send_mail_helper(
        template, None, [email], {"resources": resources, "recipient_string": recipient_string}
    )
Exemplo n.º 3
0
def send_fair_email(club, email, template="fair"):
    """
    Sends the SAC fair email for a club to the given email.
    """
    domain = settings.DEFAULT_DOMAIN
    context = {
        "name": club.name,
        "url": settings.VIEW_URL.format(domain=domain, club=club.code),
        "flyer_url": settings.FLYER_URL.format(domain=domain, club=club.code),
    }

    send_mail_helper(template, "Making the SAC Fair Easier for You", [email], context)
Exemplo n.º 4
0
def send_reminder_to_club(club):
    """
    Sends an email reminder to clubs to update their information.
    """
    receivers = None
    staff = club.members.filter(membership__role__lte=Membership.ROLE_OFFICER)

    # calculate email recipients
    if staff.exists():
        # if there are staff members that can edit the page, send the update email to them
        receivers = list(staff.values_list("email", flat=True))
    elif club.email:
        invites = club.membershipinvite_set.filter(
            active=True, role__lte=Membership.ROLE_OFFICER)
        if invites.exists():
            # if there are existing invites, resend the invite emails
            for invite in invites:
                if invite.role <= Membership.ROLE_OWNER:
                    invite.send_owner_invite()
                else:
                    invite.send_mail()
            return True
        else:
            # if there are no owner-level invites or members, create and send an owner invite
            if club.email:
                invite = MembershipInvite.objects.create(
                    club=club,
                    email=club.email,
                    creator=None,
                    role=Membership.ROLE_OWNER,
                    title="Owner",
                    auto=True,
                )
                invite.send_owner_invite()
                return True
            else:
                return False

    # send email if recipients exist
    if receivers is not None:
        domain = settings.DEFAULT_DOMAIN
        context = {
            "name": club.name,
            "url": settings.EDIT_URL.format(domain=domain, club=club.code),
            "view_url": settings.VIEW_URL.format(domain=domain,
                                                 club=club.code),
        }

        send_mail_helper("remind", "Reminder to Update Your Club's Page",
                         receivers, context)
        return True
    return False
Exemplo n.º 5
0
    def send_application_notifications(self):
        """
        Send notifications about application deadlines three days before the deadline, for students
        that have subscribed to those organizations.

        Ignore students that have already graduated and students that are already in the club.
        """
        now = timezone.now() + datetime.timedelta(days=3)
        apps = ClubApplication.objects.filter(
            Q(club__subscribe__person__profile__graduation_year__gte=now.year +
              (now.month >= 6))
            |
            Q(club__subscribe__person__profile__graduation_year__isnull=True),
            application_end_time__date=now.date(),
        ).values_list(
            "club__code",
            "club__name",
            "club__subscribe__person__email",
            "club__subscribe__person__pk",
        )

        # compute users already in clubs
        already_in_club = set(
            Membership.objects.filter(club__code__in=[x[0] for x in apps],
                                      person__pk__in=[x[3] for x in apps
                                                      ]).values_list(
                                                          "club__code",
                                                          "person__pk"))

        # group clubs by user
        emails = collections.defaultdict(list)
        for code, name, email, user_pk in apps:
            if (code, user_pk) not in already_in_club:
                emails[email].append(
                    (name,
                     settings.APPLY_URL.format(domain=settings.DOMAIN,
                                               club=code)))

        # send out one email per user
        for email, data in emails.items():
            context = {"clubs": data}
            send_mail_helper(
                "application_deadline_reminder",
                f"{len(data)} club(s) have application deadlines approaching",
                [email],
                context,
            )

        self.stdout.write(
            self.style.SUCCESS(
                f"Sent application deadline reminder to {len(emails)} user(s)")
        )
Exemplo n.º 6
0
def send_wc_intro_email(emails, clubs, recipient_string, template="wc_intro"):
    """
    Send the Hub@Penn introduction email given the email and the list of resources.
    """

    send_mail_helper(template, None, emails, {"clubs": clubs, "recipient_string": recipient_string})
Exemplo n.º 7
0
    def handle(self, *args, **kwargs):
        dry_run = kwargs["dry_run"]
        only_sheet = kwargs["only_sheet"]
        action = kwargs["type"]
        verbosity = kwargs["verbosity"]
        include_staff = kwargs["include_staff"]
        role = kwargs["role"]
        role_mapping = {k: v for k, v in Membership.ROLE_CHOICES}

        email_file = kwargs["emails"]
        test_email = kwargs.get("test", None)

        # download file if url
        if email_file is not None and re.match("^https?://", email_file, re.I):
            tf = tempfile.NamedTemporaryFile(delete=False)
            resp = requests.get(email_file)
            tf.write(resp.content)
            self.stdout.write(f"Downloaded '{email_file}' to '{tf.name}'.")
            email_file = tf.name
            tf.close()

        # handle custom Hub@Penn intro email
        if action in {
            "hap_intro",
            "hap_intro_remind",
            "hap_second_round",
            "hap_partner_communication",
        }:
            people = collections.defaultdict(dict)

            if action == "hap_partner_communication":
                emails = (
                    Membership.objects.filter(role__lte=Membership.ROLE_OFFICER)
                    .values_list("person__email", flat=True)
                    .distinct()
                )
                if test_email is not None:
                    emails = [test_email]
                for email in emails:
                    if not dry_run:
                        send_mail_helper("communication_to_partners", None, [email], {})
                        self.stdout.write(f"Sent {action} email to {email}")
                    else:
                        self.stdout.write(f"Would have sent {action} email to {email}")
                return

            # read recipients from csv file
            with open(email_file, "r") as f:
                header = [
                    re.sub(r"\W+", "", h.lower().strip().replace(" ", "_"))
                    for h in f.readline().split(",")
                ]
                reader = csv.DictReader(f, fieldnames=header)
                try:
                    for line in reader:
                        name = line["name"].strip()
                        email = line["email"].strip()
                        if "contact" in line:
                            contact = line["contact"].strip()
                        else:
                            contact = ""
                        if test_email is not None:
                            email = test_email
                        if name and email:
                            if email in people.keys():
                                people[email]["resources"].append(name)
                                people[email]["contacts"].append(contact)
                            else:
                                people[email]["resources"] = [name]
                                people[email]["contacts"] = [contact]
                except KeyError as e:
                    raise ValueError(
                        "Ensure the spreadsheet has a header with the 'name' and 'email' columns."
                    ) from e

            # send emails grouped by recipients
            for email, context in people.items():
                contacts = list(set(context["contacts"]))  # No duplicate names
                contacts = list(filter(lambda x: x != "", contacts))  # No empty string names
                if len(contacts) == 0:
                    contacts.append("Staff member")

                # Format names in comma separated form
                recipient_string = ", ".join(contacts)

                resources = context["resources"]
                if not dry_run:
                    send_hap_intro_email(
                        email,
                        resources,
                        recipient_string,
                        template={
                            "hap_intro": "intro",
                            "hap_intro_remind": "intro_remind",
                            "hap_second_round": "second_round",
                            "wc_intro": "wc_intro",
                        }[action],
                    )
                    self.stdout.write(
                        f"Sent {action} email to {email} (recipients: "
                        + f"{recipient_string}) for groups: {resources}"
                    )
                else:
                    self.stdout.write(
                        f"Would have sent {action} email to {email} (recipients: "
                        + f"{recipient_string}) for groups: {resources}"
                    )
            self.stdout.write(f"Sent out {len(people)} emails!")
            return

        if action in {"wc_intro"}:
            people = collections.defaultdict(dict)

            # read recipients from csv file
            with open(email_file, "r") as f:
                header = [
                    re.sub(r"\W+", "", h.lower().strip().replace(" ", "_"))
                    for h in f.readline().split(",")
                ]
                reader = csv.DictReader(f, fieldnames=header)
                try:
                    for line in reader:
                        name = line["name"].strip()
                        email = line["email"].strip()
                        contact = line["contact"].strip()
                        if test_email is not None:
                            email = test_email
                        if name in people.keys():
                            people[name]["emails"].append(email)
                            people[name]["contacts"].append(contact)
                        else:
                            people[name]["emails"] = [email]
                            people[name]["contacts"] = [contact]
                except KeyError as e:
                    raise ValueError(
                        "Ensure the spreadsheet has a header with the 'name' and 'email' columns."
                    ) from e

            # send emails grouped by recipients
            for name, context in people.items():
                emails = list(set(context["emails"]))  # No duplicate names
                contacts = list(set(context["contacts"]))  # No duplicate names
                contacts = list(filter(lambda x: x != "", contacts))  # No empty string names
                if len(contacts) == 0:
                    contacts.append("Staff member")

                # Format names in comma separated form
                recipient_string = ", ".join(contacts)

                clubs = [name]
                if not dry_run:
                    send_wc_intro_email(
                        emails, clubs, recipient_string, template={"wc_intro": "wc_intro"}[action],
                    )
                    self.stdout.write(
                        f"Sent {action} email to {email} (recipients: "
                        + f"{recipient_string}) for groups: {clubs}"
                    )
                else:
                    self.stdout.write(
                        f"Would have sent {action} email to {email} (recipients: "
                        + f"{recipient_string}) for groups: {clubs}"
                    )
            return

        # get club whitelist
        clubs_whitelist = [club.strip() for club in (kwargs.get("clubs") or "").split(",")]
        clubs_whitelist = [club for club in clubs_whitelist if club]

        found_whitelist = set(
            Club.objects.filter(code__in=clubs_whitelist).values_list("code", flat=True)
        )
        missing = set(clubs_whitelist) - found_whitelist
        if missing:
            raise CommandError(f"Invalid club codes in clubs parameter: {missing}")

        # load fair
        now = timezone.now()
        if action in {"virtual_fair", "urgent_virtual_fair", "post_virtual_fair"}:
            fair_id = kwargs.get("fair")
            if fair_id is not None:
                fair = ClubFair.objects.get(id=fair_id)
            else:
                fair = ClubFair.objects.filter(end_time__gte=now).order_by("start_time").first()
            if fair is None:
                raise CommandError("Could not find an upcoming activities fair!")

        # handle sending out virtual fair emails
        if action == "virtual_fair":
            clubs = fair.participating_clubs.all()
            if clubs_whitelist:
                self.stdout.write(f"Using clubs whitelist: {clubs_whitelist}")
                clubs = clubs.filter(code__in=clubs_whitelist)
            self.stdout.write(f"Found {clubs.count()} clubs participating in the {fair.name} fair.")
            extra = kwargs.get("extra", False)
            limit = kwargs.get("limit", False)
            self.stdout.write(f"Extra flag status: {extra}")
            for club in clubs:
                emails = [test_email] if test_email else None
                emails_disp = emails or "officers"
                if limit:
                    if club.events.filter(
                        ~Q(url="") & ~Q(url__isnull=True), start_time__gte=now, type=Event.FAIR
                    ).exists():
                        self.stdout.write(f"Skipping {club.name}, fair event already set up.")
                        continue
                if not dry_run:
                    status = club.send_virtual_fair_email(fair=fair, emails=emails, extra=extra)
                    self.stdout.write(
                        f"Sent virtual fair email to {club.name} ({emails_disp})... -> {status}"
                    )
                else:
                    self.stdout.write(
                        f"Would have sent virtual fair email to {club.name} ({emails_disp})..."
                    )
            return
        elif action == "urgent_virtual_fair":
            clubs = fair.participating_clubs.filter(
                Q(events__url="") | Q(events__url__isnull=True),
                events__type=Event.FAIR,
                events__start_time__gte=fair.start_time,
                events__end_time__lte=fair.end_time,
            )
            if clubs_whitelist:
                self.stdout.write(f"Using clubs whitelist: {clubs_whitelist}")
                clubs = clubs.filter(code__in=clubs_whitelist)

            self.stdout.write(f"{clubs.count()} clubs have not registered for the {fair.name}.")
            extra = kwargs.get("extra", False)
            for club in clubs.distinct():
                emails = [test_email] if test_email else None
                emails_disp = emails or "officers"
                if not dry_run:
                    self.stdout.write(
                        f"Sending fair urgent reminder for {club.name} to {emails_disp}..."
                    )
                    club.send_virtual_fair_email(
                        email="urgent", fair=fair, extra=extra, emails=emails
                    )
                else:
                    self.stdout.write(
                        f"Would have sent fair urgent reminder for {club.name} to {emails_disp}..."
                    )

            # don't continue
            return
        elif action == "post_virtual_fair":
            clubs = fair.participating_clubs.filter(
                events__type=Event.FAIR,
                events__start_time__gte=fair.start_time,
                events__end_time__lte=fair.end_time,
            )
            if clubs_whitelist:
                clubs = clubs.filter(code__in=clubs_whitelist)

            self.stdout.write(f"{clubs.count()} post fair emails to send to participants.")
            for club in clubs.distinct():
                self.stdout.write(f"Sending post fair reminder to {club.name}...")
                if not dry_run:
                    club.send_virtual_fair_email(email="post")

            return

        # handle all other email events
        if only_sheet:
            clubs = Club.objects.all()
        else:
            # find all clubs without owners or owner invitations
            clubs = Club.objects.annotate(
                owner_count=Count(
                    "membership", filter=Q(membership__role__lte=Membership.ROLE_OWNER)
                ),
                invite_count=Count(
                    "membershipinvite",
                    filter=Q(membershipinvite__role__lte=Membership.ROLE_OWNER, active=True),
                ),
            ).filter(owner_count=0, invite_count=0)
            self.stdout.write(f"Found {clubs.count()} active club(s) without owners.")

        if kwargs["only_active"]:
            clubs = clubs.filter(active=True)

        if clubs_whitelist:
            clubs = clubs.filter(code__in=clubs_whitelist)

        clubs_missing = 0
        clubs_sent = 0

        # parse CSV file
        emails = {}

        # verify email file
        if email_file is not None:
            if not os.path.isfile(email_file):
                raise CommandError(f'Email file "{email_file}" does not exist!')
        elif only_sheet:
            raise CommandError("Cannot specify only sheet option without an email file!")
        else:
            self.stdout.write(self.style.WARNING("No email spreadsheet file specified!"))

        # load email file
        if email_file is not None:
            with open(email_file, "r") as f:
                reader = csv.reader(f)
                for line in reader:
                    if not line:
                        self.stdout.write(self.style.WARNING("Skipping empty line in CSV file..."))
                        continue
                    raw_name = line[0].strip()
                    club = fuzzy_lookup_club(raw_name)

                    if club is not None:
                        if verbosity >= 2:
                            self.stdout.write(f"Mapped {raw_name} -> {club.name} ({club.code})")
                        clubs_sent += 1
                        emails[club.id] = [x.strip() for x in line[1].split(",")]
                    else:
                        clubs_missing += 1
                        self.stdout.write(
                            self.style.WARNING(f"Could not find club matching {raw_name}!")
                        )

        # send out emails
        for club in clubs:
            if club.email:
                receivers = [club.email]
                if club.id in emails:
                    if only_sheet:
                        receivers = emails[club.id]
                    else:
                        receivers += emails[club.id]
                elif only_sheet:
                    continue
                if include_staff:
                    receivers += list(
                        club.membership_set.filter(
                            role__lte=Membership.ROLE_OFFICER, person__email__isnull=False
                        )
                        .exclude(person__email="")
                        .values_list("person__email", flat=True)
                    )
                receivers = list(set(receivers))
                receivers_str = ", ".join(receivers)
                self.stdout.write(
                    self.style.SUCCESS(f"Sending {action} email for {club.name} to {receivers_str}")
                )
                for receiver in receivers:
                    if not dry_run:
                        if action == "invite":
                            existing_membership = Membership.objects.filter(
                                person__email=receiver, club=club
                            )
                            if not existing_membership.exists():
                                existing_invite = MembershipInvite.objects.filter(
                                    club=club, email=receiver, active=True
                                )
                                if not existing_invite.exists():
                                    invite = MembershipInvite.objects.create(
                                        club=club,
                                        email=receiver,
                                        creator=None,
                                        role=role,
                                        title=role_mapping[role],
                                        auto=True,
                                    )
                                else:
                                    invite = existing_invite.first()
                                if invite.role <= Membership.ROLE_OWNER:
                                    invite.send_owner_invite()
                                else:
                                    invite.send_mail()
                        elif action == "physical_fair":
                            send_fair_email(club, receiver)
                        elif action == "physical_postfair":
                            send_fair_email(club, receiver, template="postfair")

        self.stdout.write(f"Sent {clubs_sent} email(s), {clubs_missing} missing club(s)")