Beispiel #1
0
class NotificationTemplate(models.Model):
    """Notification template."""

    template = models.ForeignKey(EmailTemplate, blank=False)
    from_email = models.CharField(_("Email From"),
                                  max_length=254,
                                  validators=[validate_email_with_name])
    to = CommaSeparatedEmailField(_("Email To"),
                                  null=False,
                                  blank=False,
                                  default=[])
    cc = CommaSeparatedEmailField(_("Cc"), blank=True, default=[])
    bcc = CommaSeparatedEmailField(("Bcc"), blank=True, default=[])

    def __str__(self):
        """String representation."""
        return '{self.template}'.format(self=self)

    @staticmethod
    def autocomplete_search_fields():
        """Auto complete search fields."""
        return (
            "id__iexact",
            "template__name__icontains",
        )

    def notify(self, context):
        """Notify with given context."""
        mail.send(template=self.template.name,
                  recipients=self.to,
                  sender=self.from_email,
                  context=context)
Beispiel #2
0
class Email(models.Model):
    """
    A model to hold email information.
    """

    PRIORITY_CHOICES = [(PRIORITY.low, _("low")),
                        (PRIORITY.medium, _("medium")),
                        (PRIORITY.high, _("high")), (PRIORITY.now, _("now"))]
    STATUS_CHOICES = [(STATUS.sent, _("sent")), (STATUS.failed, _("failed")),
                      (STATUS.queued, _("queued"))]

    from_email = models.CharField(_("Email From"),
                                  max_length=254,
                                  validators=[validate_email_with_name])
    to = CommaSeparatedEmailField(_("Email To"))
    cc = CommaSeparatedEmailField(_("Cc"))
    bcc = CommaSeparatedEmailField(("Bcc"))
    subject = models.CharField(_("Subject"), max_length=989, blank=True)
    message = models.TextField(_("Message"), blank=True)
    html_message = models.TextField(_("HTML Message"), blank=True)
    """
    Emails with 'queued' status will get processed by ``send_queued`` command.
    Status field will then be set to ``failed`` or ``sent`` depending on
    whether it's successfully delivered.
    """
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              db_index=True,
                                              blank=True,
                                              null=True)
    priority = models.PositiveSmallIntegerField(choices=PRIORITY_CHOICES,
                                                blank=True,
                                                null=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    last_updated = models.DateTimeField(db_index=True, auto_now=True)
    scheduled_time = models.DateTimeField(blank=True, null=True, db_index=True)
    headers = JSONField(blank=True, null=True)
    template = models.ForeignKey('post_office.EmailTemplate',
                                 blank=True,
                                 null=True)
    context = context_field_class(blank=True, null=True)
    backend_alias = models.CharField(blank=True, default='', max_length=64)

    class Meta:
        app_label = 'post_office'

    def __str__(self):
        return u'%s' % self.to

    def email_message(self, connection=None):
        """
        Returns a django ``EmailMessage`` or ``EmailMultiAlternatives`` object,
        depending on whether html_message is empty.
        """
        subject = smart_text(self.subject)

        if self.template is not None and not self.html_message:
            _context = Context(self.context)
            subject = Template(self.template.subject).render(_context)
            message = Template(self.template.content).render(_context)
            html_message = Template(
                self.template.html_content).render(_context)

        else:
            subject = self.subject
            message = self.message
            html_message = self.html_message

        if html_message:
            msg = EmailMultiAlternatives(subject=subject,
                                         body=message,
                                         from_email=self.from_email,
                                         to=self.to,
                                         bcc=self.bcc,
                                         cc=self.cc,
                                         connection=connection,
                                         headers=self.headers)
            msg.attach_alternative(html_message, "text/html")
        else:
            msg = EmailMessage(subject=subject,
                               body=message,
                               from_email=self.from_email,
                               to=self.to,
                               bcc=self.bcc,
                               cc=self.cc,
                               connection=connection,
                               headers=self.headers)

        for attachment in self.attachments.all():
            msg.attach(attachment.name, attachment.file.read())

        return msg

    def dispatch(self, log_level=None, disconnect_after_delivery=True):
        """
        Actually send out the email and log the result
        """

        if log_level is None:
            log_level = get_log_level()

        connection = None
        try:
            connection = connections[self.backend_alias or 'default']
            self.email_message(connection=connection).send()
            status = STATUS.sent
            message = ''
            exception_type = ''
        except Exception as e:
            status = STATUS.failed
            message = str(e)
            exception_type = type(e).__name__

        if connection and disconnect_after_delivery:
            connection.close()

        self.status = status
        self.save()

        # If log level is 0, log nothing, 1 logs only sending failures
        # and 2 means log both successes and failures
        if log_level == 1:
            if status == STATUS.failed:
                self.logs.create(status=status,
                                 message=message,
                                 exception_type=exception_type)
        elif log_level == 2:
            self.logs.create(status=status,
                             message=message,
                             exception_type=exception_type)

        return status

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(Email, self).save(*args, **kwargs)
class Email(models.Model):
    """
    A model to hold email information.
    """

    PRIORITY_CHOICES = [(PRIORITY.low, _("low")),
                        (PRIORITY.medium, _("medium")),
                        (PRIORITY.high, _("high")), (PRIORITY.now, _("now"))]
    STATUS_CHOICES = [(STATUS.sent, _("sent")), (STATUS.failed, _("failed")),
                      (STATUS.queued, _("queued")),
                      (STATUS.requeued, _("requeued"))]

    from_email = models.CharField(_("Email From"),
                                  max_length=254,
                                  validators=[validate_email_with_name])
    to = CommaSeparatedEmailField(_("Email To"))
    cc = CommaSeparatedEmailField(_("Cc"))
    bcc = CommaSeparatedEmailField(_("Bcc"))
    subject = models.CharField(_("Subject"), max_length=989, blank=True)
    message = models.TextField(_("Message"), blank=True)
    html_message = models.TextField(_("HTML Message"), blank=True)
    """
    Emails with 'queued' status will get processed by ``send_queued`` command.
    Status field will then be set to ``failed`` or ``sent`` depending on
    whether it's successfully delivered.
    """
    status = models.PositiveSmallIntegerField(_("Status"),
                                              choices=STATUS_CHOICES,
                                              db_index=True,
                                              blank=True,
                                              null=True)
    priority = models.PositiveSmallIntegerField(_("Priority"),
                                                choices=PRIORITY_CHOICES,
                                                blank=True,
                                                null=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    last_updated = models.DateTimeField(db_index=True, auto_now=True)
    scheduled_time = models.DateTimeField(
        _("Scheduled Time"),
        blank=True,
        null=True,
        db_index=True,
        help_text=_("The scheduled sending time"))
    expires_at = models.DateTimeField(
        _("Expires"),
        blank=True,
        null=True,
        help_text=_("Email won't be sent after this timestamp"))
    number_of_retries = models.PositiveIntegerField(null=True, blank=True)
    headers = JSONField(_('Headers'), blank=True, null=True)
    template = models.ForeignKey('post_office.EmailTemplate',
                                 blank=True,
                                 null=True,
                                 verbose_name=_("Email template"),
                                 on_delete=models.CASCADE)
    context = context_field_class(_('Context'), blank=True, null=True)
    backend_alias = models.CharField(_("Backend alias"),
                                     blank=True,
                                     default='',
                                     max_length=64)

    class Meta:
        app_label = 'post_office'
        verbose_name = pgettext_lazy("Email address", "Email")
        verbose_name_plural = pgettext_lazy("Email addresses", "Emails")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._cached_email_message = None

    def __str__(self):
        return '%s' % self.to

    def email_message(self):
        """
        Returns Django EmailMessage object for sending.
        """
        if self._cached_email_message:
            return self._cached_email_message

        return self.prepare_email_message()

    def prepare_email_message(self):
        """
        Returns a django ``EmailMessage`` or ``EmailMultiAlternatives`` object,
        depending on whether html_message is empty.
        """
        if get_override_recipients():
            self.to = get_override_recipients()

        if self.template is not None:
            engine = get_template_engine()
            subject = engine.from_string(self.template.subject).render(
                self.context)
            plaintext_message = engine.from_string(
                self.template.content).render(self.context)
            multipart_template = engine.from_string(self.template.html_content)
            html_message = multipart_template.render(self.context)

        else:
            subject = smart_str(self.subject)
            plaintext_message = self.message
            multipart_template = None
            html_message = self.html_message

        connection = connections[self.backend_alias or 'default']
        if isinstance(self.headers, dict) or self.expires_at:
            headers = dict(self.headers or {})
            if self.expires_at:
                headers.update({
                    'Expires':
                    self.expires_at.strftime("%a, %-d %b %H:%M:%S %z")
                })
        else:
            headers = None

        if html_message:
            if plaintext_message:
                msg = EmailMultiAlternatives(subject=subject,
                                             body=plaintext_message,
                                             from_email=self.from_email,
                                             to=self.to,
                                             bcc=self.bcc,
                                             cc=self.cc,
                                             headers=headers,
                                             connection=connection)
                msg.attach_alternative(html_message, "text/html")
            else:
                msg = EmailMultiAlternatives(subject=subject,
                                             body=html_message,
                                             from_email=self.from_email,
                                             to=self.to,
                                             bcc=self.bcc,
                                             cc=self.cc,
                                             headers=headers,
                                             connection=connection)
                msg.content_subtype = 'html'
            if hasattr(multipart_template, 'attach_related'):
                multipart_template.attach_related(msg)

        else:
            msg = EmailMessage(subject=subject,
                               body=plaintext_message,
                               from_email=self.from_email,
                               to=self.to,
                               bcc=self.bcc,
                               cc=self.cc,
                               headers=headers,
                               connection=connection)

        for attachment in self.attachments.all():
            if attachment.headers:
                mime_part = MIMENonMultipart(*attachment.mimetype.split('/'))
                mime_part.set_payload(attachment.file.read())
                for key, val in attachment.headers.items():
                    try:
                        mime_part.replace_header(key, val)
                    except KeyError:
                        mime_part.add_header(key, val)
                msg.attach(mime_part)
            else:
                msg.attach(attachment.name,
                           attachment.file.read(),
                           mimetype=attachment.mimetype or None)
            attachment.file.close()

        self._cached_email_message = msg
        return msg

    def dispatch(self,
                 log_level=None,
                 disconnect_after_delivery=True,
                 commit=True):
        """
        Sends email and log the result.
        """
        try:
            self.email_message().send()
            status = STATUS.sent
            message = ''
            exception_type = ''
        except Exception as e:
            status = STATUS.failed
            message = str(e)
            exception_type = type(e).__name__

            # If run in a bulk sending mode, reraise and let the outer
            # layer handle the exception
            if not commit:
                raise

        if commit:
            self.status = status
            self.save(update_fields=['status'])

            if log_level is None:
                log_level = get_log_level()

            # If log level is 0, log nothing, 1 logs only sending failures
            # and 2 means log both successes and failures
            if log_level == 1:
                if status == STATUS.failed:
                    self.logs.create(status=status,
                                     message=message,
                                     exception_type=exception_type)
            elif log_level == 2:
                self.logs.create(status=status,
                                 message=message,
                                 exception_type=exception_type)

        return status

    def clean(self):
        if self.scheduled_time and self.expires_at and self.scheduled_time > self.expires_at:
            raise ValidationError(
                _("The scheduled time may not be later than the expires time.")
            )

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)
class Email(models.Model):
    """
    A model to hold email information.
    """

    PRIORITY_CHOICES = [(PRIORITY.low, _("low")), (PRIORITY.medium, _("medium")),
                        (PRIORITY.high, _("high")), (PRIORITY.now, _("now"))]
    STATUS_CHOICES = [(STATUS.sent, _("sent")), (STATUS.failed, _("failed")),
                      (STATUS.queued, _("queued"))]

    from_email = models.CharField(_("Email From"), max_length=254,
                                  validators=[validate_email_with_name])
    to = CommaSeparatedEmailField(_("Email To"))
    cc = CommaSeparatedEmailField(_("Cc"))
    bcc = CommaSeparatedEmailField(_("Bcc"))
    subject = models.CharField(_("Subject"), max_length=989, blank=True)
    message = models.TextField(_("Message"), blank=True)
    html_message = models.TextField(_("HTML Message"), blank=True)
    """
    Emails with 'queued' status will get processed by ``send_queued`` command.
    Status field will then be set to ``failed`` or ``sent`` depending on
    whether it's successfully delivered.
    """
    status = models.PositiveSmallIntegerField(
        _("Status"),
        choices=STATUS_CHOICES, db_index=True,
        blank=True, null=True)
    priority = models.PositiveSmallIntegerField(_("Priority"),
                                                choices=PRIORITY_CHOICES,
                                                blank=True, null=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    last_updated = models.DateTimeField(db_index=True, auto_now=True)
    scheduled_time = models.DateTimeField(_('The scheduled sending time'),
                                          blank=True, null=True, db_index=True)
    headers = JSONField(_('Headers'), blank=True, null=True)
    template = models.ForeignKey('post_office.EmailTemplate', blank=True,
                                 null=True, verbose_name=_('Email template'),
                                 on_delete=models.CASCADE)
    context = context_field_class(_('Context'), blank=True, null=True)
    backend_alias = models.CharField(_('Backend alias'), blank=True, default='',
                                     max_length=64)
    site = models.ForeignKey(
        sites_models.Site,
        null=True,
        blank=True,
        related_name='emails',
        verbose_name=_('Site'),
        help_text=_('Related site object.')
    )

    objects = EmailManager()

    class Meta:
        app_label = 'post_office'
        verbose_name = pgettext_lazy("Email address", "Email")
        verbose_name_plural = pgettext_lazy("Email addresses", "Emails")

    def __init__(self, *args, **kwargs):
        super(Email, self).__init__(*args, **kwargs)
        self._cached_email_message = None

    def __str__(self):
        return u'%s' % self.to

    def email_message(self):
        """
        Returns Django EmailMessage object for sending.
        """
        if self._cached_email_message:
            return self._cached_email_message

        return self.prepare_email_message()

    def prepare_email_message(self):
        """
        Returns a django ``EmailMessage`` or ``EmailMultiAlternatives`` object,
        depending on whether html_message is empty.
        """
        subject = smart_text(self.subject)

        if self.template is not None:
            _context = Context(self.context)
            subject = Template(self.template.subject).render(_context)
            message = Template(self.template.content).render(_context)
            html_message = Template(self.template.html_content).render(_context)

        else:
            subject = self.subject
            message = self.message
            html_message = self.html_message

        connection = connections[self.backend_alias or 'default']

        if html_message:
            msg = EmailMultiAlternatives(
                subject=subject, body=message, from_email=self.from_email,
                to=self.to, bcc=self.bcc, cc=self.cc,
                headers=self.headers, connection=connection)
            msg.attach_alternative(html_message, "text/html")
        else:
            msg = EmailMessage(
                subject=subject, body=message, from_email=self.from_email,
                to=self.to, bcc=self.bcc, cc=self.cc,
                headers=self.headers, connection=connection)

        for attachment in self.attachments.all():
            try:
                msg.attach(attachment.name, attachment.file.read(), mimetype=attachment.mimetype or None)
                attachment.file.close()
            except Exception as e:
                logger.exception('failed to attach file: %s' % e)

        self._cached_email_message = msg
        return msg

    def dispatch(self, log_level=None,
                 disconnect_after_delivery=True, commit=True):
        """
        Sends email and log the result.
        """
        try:
            self.email_message().send()
            status = STATUS.sent
            message = ''
            exception_type = ''
        except Exception as e:
            status = STATUS.failed
            message = str(e)
            exception_type = type(e).__name__

            # If run in a bulk sending mode, reraise and let the outer
            # layer handle the exception
            if not commit:
                raise

        if commit:
            self.status = status
            self.save(update_fields=['status'])

            if log_level is None:
                log_level = get_log_level()

            # If log level is 0, log nothing, 1 logs only sending failures
            # and 2 means log both successes and failures
            if log_level == 1:
                if status == STATUS.failed:
                    self.logs.create(status=status, message=message,
                                     exception_type=exception_type)
            elif log_level == 2:
                self.logs.create(status=status, message=message,
                                 exception_type=exception_type)

        return status

    def save(self, *args, **kwargs):
        site = sites_models.Site.objects.get_current()
        if site.pk:
            self.site = site
        self.full_clean()
        return super(Email, self).save(*args, **kwargs)
Beispiel #5
0
class Email(models.Model):
    """
    A model to hold email information.
    """

    PRIORITY_CHOICES = [(PRIORITY.low, 'low'), (PRIORITY.medium, 'medium'),
                        (PRIORITY.high, 'high'), (PRIORITY.now, 'now')]
    STATUS_CHOICES = [(STATUS.sent, 'sent'), (STATUS.failed, 'failed'),
                      (STATUS.queued, 'queued')]

    from_email = models.CharField(max_length=254,
                                  validators=[validate_email_with_name])
    to = CommaSeparatedEmailField()
    cc = CommaSeparatedEmailField()
    bcc = CommaSeparatedEmailField()
    subject = models.CharField(max_length=255, blank=True)
    message = models.TextField(blank=True)
    html_message = models.TextField(blank=True)
    """
    Emails with 'queued' status will get processed by ``send_queued`` command.
    Status field will then be set to ``failed`` or ``sent`` depending on
    whether it's successfully delivered.
    """
    status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES,
                                              db_index=True,
                                              blank=True,
                                              null=True)
    priority = models.PositiveSmallIntegerField(choices=PRIORITY_CHOICES,
                                                blank=True,
                                                null=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    last_updated = models.DateTimeField(db_index=True, auto_now=True)
    scheduled_time = models.DateTimeField(blank=True, null=True, db_index=True)
    headers = JSONField(blank=True, null=True)
    template = models.ForeignKey('post_office.EmailTemplate',
                                 blank=True,
                                 null=True)
    context = context_field_class(blank=True, null=True)

    def __unicode__(self):
        return u'%s' % self.to

    def email_message(self, connection=None):
        """
        Returns a django ``EmailMessage`` or ``EmailMultiAlternatives`` object
        from a ``Message`` instance, depending on whether html_message is empty.
        """
        subject = smart_text(self.subject)

        if self.template is not None:
            _context = Context(self.context)
            subject = Template(self.template.subject).render(_context)
            message = Template(self.template.content).render(_context)
            html_message = Template(
                self.template.html_content).render(_context)
        else:
            subject = self.subject
            message = self.message
            html_message = self.html_message

        if html_message:
            msg = EmailMultiAlternatives(subject=subject,
                                         body=message,
                                         from_email=self.from_email,
                                         to=self.to,
                                         bcc=self.bcc,
                                         cc=self.cc,
                                         connection=connection,
                                         headers=self.headers)
            msg.attach_alternative(html_message, "text/html")
        else:
            msg = EmailMessage(subject=subject,
                               body=message,
                               from_email=self.from_email,
                               to=self.to,
                               bcc=self.bcc,
                               cc=self.cc,
                               connection=connection,
                               headers=self.headers)

        for attachment in self.attachments.all():
            msg.attach(attachment.name, attachment.file.read())

        return msg

    def dispatch(self, connection=None, log_level=None):
        """
        Actually send out the email and log the result
        """
        connection_opened = False

        if log_level is None:
            log_level = get_log_level()

        try:
            if connection is None:
                connection = get_connection(get_email_backend())
                connection.open()
                connection_opened = True

            self.email_message(connection=connection).send()
            status = STATUS.sent
            message = ''
            exception_type = ''

            if connection_opened:
                connection.close()

        except Exception:
            status = STATUS.failed
            exception, message, _ = sys.exc_info()
            exception_type = exception.__name__

        self.status = status
        self.save()

        # If log level is 0, log nothing, 1 logs only sending failures
        # and 2 means log both successes and failures
        if log_level == 1:
            if status == STATUS.failed:
                self.logs.create(status=status,
                                 message=message,
                                 exception_type=exception_type)
        elif log_level == 2:
            self.logs.create(status=status,
                             message=message,
                             exception_type=exception_type)

        return status

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(Email, self).save(*args, **kwargs)
Beispiel #6
0
class Email(models.Model):
    """
    A model to hold email information.
    """
    from django.contrib.auth import get_user_model
    from django.contrib.auth.models import Group
    from django.db.models import Q
    from django.conf import settings
    User = get_user_model()

    PRIORITY_CHOICES = [(PRIORITY.low, _("low")), (PRIORITY.medium, _("medium")),
                        (PRIORITY.high, _("high")), (PRIORITY.now, _("now"))]
    STATUS_CHOICES = [(STATUS.sent, _("sent")), (STATUS.failed, _("failed")),
                      (STATUS.queued, _("queued"))]

    from_email = models.CharField(_("Email From"), max_length=254,
                                  validators=[validate_email_with_name], help_text=_("Must have permission to send through the default server!"))
    group = models.ForeignKey('auth.group', blank=True, null=True, 
                                help_text=_("Send email to all members of a group."),
                                verbose_name=_('Send to group'), 
                                on_delete=models.SET_NULL)
    to = CommaSeparatedEmailField(_("Email To"), help_text=_("Optional (if no group of recipients selected), separate addresses with a comma."))
    cc = CommaSeparatedEmailField(_("Email Cc"), help_text=_("Optional, separate multiple addresses with a comma."))
    bcc = CommaSeparatedEmailField(_("Email Bcc"), help_text=_("Optional, separate multiple addresses with a comma."))
    subject = models.CharField(_("Subject"), max_length=989, blank=True, help_text=_('Leave this blank if sending content by a template!'))
    message = models.TextField(_("Text Content"), blank=True, help_text=_('Leave this blank if sending content by a template!'))
    html_message = models.TextField(_("HTML Content"), blank=True, help_text=_('Leave this blank if sending content by a template!'))
    """
    Emails with 'queued' status will get processed by ``send_queued`` command.
    Status field will then be set to ``failed`` or ``sent`` depending on
    whether it's successfully delivered.
    """
    status = models.PositiveSmallIntegerField(
        _("Status"),
        choices=STATUS_CHOICES, db_index=True,
        blank=True, null=True)
    priority = models.PositiveSmallIntegerField(_("Priority"),
                                                choices=PRIORITY_CHOICES,
                                                blank=True, null=True)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    last_updated = models.DateTimeField(db_index=True, auto_now=True)
    scheduled_time = models.DateTimeField(_('Scheduled sending time'),
                                          blank=True, null=True, db_index=True)
    headers = JSONField(_('Headers'), blank=True, null=True)
    template = models.ForeignKey('post_office.EmailTemplate', blank=True,
                                help_text=_('If selected, will replace subject, plain-text content, and html content with content from the chosen template.'),
                                 null=True, verbose_name=_('Email template'),
                                 on_delete=models.CASCADE)
    context = context_field_class(_('Context'), blank=True, null=True)
    backend_alias = models.CharField(_('Backend alias'), blank=True, default='',
                                     max_length=64)

    class Meta:
        app_label = 'post_office'
        verbose_name = pgettext_lazy("Email address", "Email")
        verbose_name_plural = pgettext_lazy("Email addresses", "Emails")

    def __init__(self, *args, **kwargs):
        super(Email, self).__init__(*args, **kwargs)
        self._cached_email_message = None

    def __str__(self):
        return u'%s' % self.to

    def email_message(self):
        """
        Returns Django EmailMessage object for sending.
        """
        if self._cached_email_message:
            return self._cached_email_message

        return self.prepare_email_message()

    def prepare_email_message(self):
        """
        Returns a django ``EmailMessage`` or ``EmailMultiAlternatives`` object,
        depending on whether html_message is empty.
        """
        if get_override_recipients():
            self.to = get_override_recipients()

        if self.template is not None:
            engine = get_template_engine()
            subject = engine.from_string(self.template.subject).render(self.context)
            plaintext_message = engine.from_string(self.template.content).render(self.context)
            multipart_template = engine.from_string(self.template.html_content)
            html_message = multipart_template.render(self.context)

        else:
            subject = smart_text(self.subject)
            plaintext_message = self.message
            multipart_template = None
            html_message = self.html_message

        connection = connections[self.backend_alias or 'default']

        if html_message:
            if plaintext_message:
                msg = EmailMultiAlternatives(
                    subject=subject, body=plaintext_message, from_email=self.from_email,
                    to=self.to, bcc=self.bcc, cc=self.cc,
                    headers=self.headers, connection=connection)
                msg.attach_alternative(html_message, "text/html")
            else:
                msg = EmailMultiAlternatives(
                    subject=subject, body=html_message, from_email=self.from_email,
                    to=self.to, bcc=self.bcc, cc=self.cc,
                    headers=self.headers, connection=connection)
                msg.content_subtype = 'html'
            if hasattr(multipart_template, 'attach_related'):
                multipart_template.attach_related(msg)

        else:
            msg = EmailMessage(
                subject=subject, body=plaintext_message, from_email=self.from_email,
                to=self.to, bcc=self.bcc, cc=self.cc,
                headers=self.headers, connection=connection)

        for attachment in self.attachments.all():
            if attachment.headers:
                mime_part = MIMENonMultipart(*attachment.mimetype.split('/'))
                mime_part.set_payload(attachment.file.read())
                for key, val in attachment.headers.items():
                    try:
                        mime_part.replace_header(key, val)
                    except KeyError:
                        mime_part.add_header(key, val)
                msg.attach(mime_part)
            else:
                msg.attach(attachment.name, attachment.file.read(), mimetype=attachment.mimetype or None)
            attachment.file.close()

        self._cached_email_message = msg
        return msg

    def dispatch(self, log_level=None,
                 disconnect_after_delivery=True, commit=True):
        """
        Sends email and log the result.
        """
        try:
            self.email_message().send()
            status = STATUS.sent
            message = ''
            exception_type = ''
        except Exception as e:
            status = STATUS.failed
            message = str(e)
            exception_type = type(e).__name__

            # If run in a bulk sending mode, reraise and let the outer
            # layer handle the exception
            if not commit:
                raise

        if commit:
            self.status = status
            self.save(update_fields=['status'])

            if log_level is None:
                log_level = get_log_level()

            # If log level is 0, log nothing, 1 logs only sending failures
            # and 2 means log both successes and failures
            if log_level == 1:
                if status == STATUS.failed:
                    self.logs.create(status=status, message=message,
                                     exception_type=exception_type)
            elif log_level == 2:
                self.logs.create(status=status, message=message,
                                 exception_type=exception_type)

        return status

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(Email, self).save(*args, **kwargs)