def confirm(self):
        if self.is_expired():
            raise EmailConfirmationExpired()
        else:
            if not self.is_verified:
                with transaction.atomic():
                    self.is_verified = True
                    self.confirmed_at = timezone.now()
                    update_fields(self, fields=('is_verified', 'confirmed_at'))

                    signals.post_email_confirm.send(
                        sender=self.__class__,
                        confirmation=self,
                    )

                    if self.is_primary and settings.EMAIL_CONFIRM_LA_SAVE_EMAIL_TO_INSTANCE:
                        self.save_email()

                    # Expire all other confirmations for the same email
                    reverse_expiry = timezone.now() - datetime.timedelta(seconds=settings.EMAIL_CONFIRM_LA_CONFIRM_EXPIRE_SEC)
                    EmailConfirmation.objects \
                        .filter(
                            content_type=self.content_type,
                            email_field_name=self.email_field_name,
                            email=self.email
                        ) \
                        .exclude(pk=self.pk) \
                        .update(send_at=reverse_expiry)

        return self
    def send(self, template_context=None):
        default_template_context = {
            'email': self.email,
            'confirmation_key': self.confirmation_key,
            'confirmation_url': self.get_confirmation_url(),
        }
        if isinstance(template_context, dict):
            template_context = dict(default_template_context.items() + template_context.items())  # merge dictionaries
        else:
            template_context = default_template_context

        subject = render_to_string('email_confirm_la/email/email_confirmation_subject.txt', template_context)
        subject = ''.join(subject.splitlines())  # remove superfluous line breaks
        body = render_to_string('email_confirm_la/email/email_confirmation_message.html', template_context)

        message = EmailMessage(subject, body, settings.DEFAULT_FROM_EMAIL, [self.email, ])
        message.content_subtype = 'html'
        # message.send()

        # TODO: send mass emails?
        connection = get_connection(settings.EMAIL_CONFIRM_LA_EMAIL_BACKEND)
        connection.send_messages([message, ])

        self.send_at = timezone.now()
        update_fields(self, fields=('send_at', ))

        signals.post_email_confirmation_send.send(
            sender=self.__class__,
            confirmation=self,
        )
    def set_email_for_object(self, email, content_object, email_field_name='email', is_primary=True, skip_verify=False, template_context=None):
        """
        只有 `is_primary=True` 時,email 才會被 save 到 content_object 的 email_field_name 欄位
        """

        content_type = ContentType.objects.get_for_model(content_object)
        try:
            confirmation = EmailConfirmation.objects.get(
                content_type=content_type,
                object_id=content_object.id,
                email_field_name=email_field_name,
                email=email,
            )
        except EmailConfirmation.DoesNotExist:
            confirmation = EmailConfirmation()
            confirmation.content_object = content_object
            confirmation.email_field_name = email_field_name
            confirmation.email = email
            confirmation.confirmation_key = utils.generate_random_token([str(content_type.id), str(content_object.id), email, ])
            confirmation.save()

            if not skip_verify:
                confirmation.send(template_context)

        if is_primary:
            confirmation = confirmation.set_primary()

        if skip_verify:
            confirmation.is_verified = True
            update_fields(confirmation, fields=('is_verified', ))

        if confirmation.is_verified and confirmation.is_primary and settings.EMAIL_CONFIRM_LA_SAVE_EMAIL_TO_INSTANCE:
            confirmation.save_email()

        return confirmation
    def save_email(self):
        content_object = self.content_object
        email_field_name = self.email_field_name
        email = self.email

        setattr(content_object, email_field_name, email)
        update_fields(content_object, fields=(email_field_name, ))

        signals.post_email_save.send(
            sender=self.__class__,
            confirmation=self,
        )
    def set_primary(self):
        try:
            old_primary = EmailConfirmation.objects.get_primary_for_object(content_object=self.content_object, email_field_name=self.email_field_name)
        except EmailConfirmation.DoesNotExist:
            pass
        else:
            if old_primary != self:
                old_primary.is_primary = False
                update_fields(old_primary, fields=('is_primary', ))

        self.is_primary = True
        update_fields(self, fields=('is_primary', ))

        return self
    def set_email_for_object(self, email, content_object, email_field_name='email', is_primary=True, skip_verify=False, template_context=None):
        """
        Add an email for `content_object` and send a confirmation mail by default.

        The email will be directly saved to `content_object.email_field_name` when `is_primary` and `skip_verify` both are true.
        """

        content_type = ContentType.objects.get_for_model(content_object)
        try:
            confirmation = EmailConfirmation.objects.get(
                content_type=content_type,
                object_id=content_object.id,
                email_field_name=email_field_name,
                email=email,
            )
        except EmailConfirmation.DoesNotExist:
            confirmation = EmailConfirmation()
            confirmation.content_object = content_object
            confirmation.email_field_name = email_field_name
            confirmation.email = email
            confirmation.confirmation_key = utils.generate_random_token([str(content_type.id), str(content_object.id), email, ])
            confirmation.save()

            if not skip_verify:
                confirmation.send(template_context)

        if is_primary:
            confirmation = confirmation.set_primary()

        if skip_verify:
            confirmation.is_verified = True
            update_fields(confirmation, fields=('is_verified', ))

        # TODO: may remove EMAIL_CONFIRM_LA_SAVE_EMAIL_TO_INSTANCE
        if confirmation.is_verified and confirmation.is_primary and settings.EMAIL_CONFIRM_LA_SAVE_EMAIL_TO_INSTANCE:
            confirmation.save_email()

        return confirmation
    def confirm(self, request):
        if not self.is_verified:
            if self.is_expired():
                raise EmailConfirmationExpired()
            with transaction.atomic():
                self.is_verified = True
                self.confirmed_at = timezone.now()
                # self.save(update_fields=['is_verified', 'confirmed_at'])
                update_fields(self, fields=('is_verified', 'confirmed_at'))

                signal_responses = signals.post_email_confirm.send(
                    sender=self.__class__,
                    confirmation=self,
                    request=request,
                )

                if self.is_primary and settings.EMAIL_CONFIRM_LA_SAVE_EMAIL_TO_INSTANCE:
                    self.save_email()

        else:
            # This email confirmation is already verified, but the user may
            # click the link twice (by accident?). The response should still
            # be helpful. Re-issue the signal.
            signal_responses = signals.post_email_confirm.send(
                sender=self.__class__,
                confirmation=self,
                request=request,
            )

        # signal.send() returns a list of tuples of the receiver and the
        # returned value from that receiver.
        for reciver, response in signal_responses:
            if response is not None:
                return response

        return None