snorklerjoe/CubeServer

View on GitHub
src/CubeServer-common/cubeserver_common/models/mail.py

Summary

Maintainability
A
1 hr
Test Coverage
"""Handles sending emails"""

import smtplib
import logging
from email.mime.text import MIMEText
from datetime import datetime
from typing import Optional, List
from bson.objectid import ObjectId

from cubeserver_common.models.config.conf import Conf
from cubeserver_common.models.utils.modelutils import PyMongoModel
from cubeserver_common.models.utils.dummycodec import DummyCodec

# from cubeserver_common.models.team import Team  # Creates circular import; removed


class Message(PyMongoModel):
    """Describes an email to be sent"""

    def __init__(
        self,
        from_name: str = "",
        from_addr: str = "",
        recipients: list[str] = [],
        subject: str = "",
        message: str = "",
        team_identifier: Optional[ObjectId] = None,
    ) -> None:
        super().__init__()
        self.from_addr = from_addr
        self.recipients = recipients
        self.from_name = from_name
        self.subject = subject
        self.message = message
        self.team_reference = team_identifier

        self.register_field("sent_at", custom_codec=DummyCodec(datetime))
        self._ignored += ["_mimetext"]

    @property
    def sender_str(self) -> str:
        return f"{self.from_name} <{self.from_addr}>"

    def send(self) -> bool:
        """Sends the message.
        Or at least tries to.
        Returns True if we think it worked."""

        if self.from_addr is None:
            raise TypeError("Cannot send empty message")

        self._mimetext = MIMEText(self.message)
        self._mimetext["Subject"] = self.subject
        self._mimetext["From"] = self.sender_str
        self._mimetext["To"] = ", ".join(self.recipients)

        success = True
        config: Conf = Conf.retrieve_instance()
        try:
            s = smtplib.SMTP(config.smtp_server)
        except OSError:
            logging.error(f"unable to connect to smtp server {config.smtp_server}")
            success = False
            return success

        try:
            s.ehlo_or_helo_if_needed()
            s.starttls()
            if config.smtp_user is not None and config.smtp_user.strip() != "":
                s.login(config.smtp_user, config.smtp_pass)
            s.sendmail(self.from_addr, self.recipients, self._mimetext.as_string())
            self.sent_at = datetime.now()
        except smtplib.SMTPException:
            logging.error("we had a little oopsie with the SMTP server...")
            success = False
        finally:
            s.quit()
            s.close()
        if success:  # Save into database of messages if it sent okay...
            self.save()
        return success

    @classmethod  # TODO: Structure other "find_by..."'s similarly to reduce repetitive code:
    def find_by_team(cls, team: ObjectId | str | None) -> List["Message"]:
        """Returns a list of Messages sent by this team"""
        if isinstance(team, ObjectId):
            team_id = team
        #        elif isinstance(team, Team):  # Circular import; removed.
        #            team_id = team.id         # TODO: Add back in with issue #134
        elif isinstance(team, str):
            team_id = ObjectId(team)
        elif team is None:
            team_id = None
        else:
            raise TypeError("Invalid type with which to filter messages by team")

        return cls.find({"team_reference": team_id})