|
| 1 | +from dataclasses import dataclass |
| 2 | +import logging |
| 3 | + |
1 | 4 | from django.core.mail import send_mail
|
2 | 5 | from django.template.loader import render_to_string
|
3 | 6 | from django.utils.html import strip_tags
|
4 | 7 |
|
5 | 8 | from .app_configurations import GetFieldFromSettings
|
6 | 9 | from .errors import InvalidTokenOrEmail
|
7 | 10 | from .token_manager import TokenManager
|
| 11 | +from .custom_types import User |
| 12 | + |
| 13 | +logger = logging.getLogger(__name__) |
8 | 14 |
|
9 | 15 |
|
10 |
| -class _VerifyEmail: |
| 16 | +@dataclass(frozen=True) |
| 17 | +class ActivationMailManager: |
11 | 18 | """
|
12 | 19 | This class does two things:
|
13 | 20 | 1. set each user as inactive and saves it
|
14 | 21 | 2. sends the email to user with that link.
|
15 | 22 | """
|
16 | 23 |
|
17 |
| - def __init__(self): |
18 |
| - self.settings = GetFieldFromSettings() |
19 |
| - self.token_manager = TokenManager() |
| 24 | + token_manager: TokenManager = TokenManager() |
| 25 | + settings: GetFieldFromSettings = GetFieldFromSettings() |
| 26 | + |
| 27 | + def _generate_verification_url( |
| 28 | + self, inactive_user: User, user_email: str, request=None |
| 29 | + ): |
| 30 | + token = self.token_manager.generate_token_for_user(inactive_user) |
| 31 | + link = ( |
| 32 | + self.token_manager.link_manager.generate_link(token, user_email) |
| 33 | + if not request |
| 34 | + else self.token_manager.link_manager.get_absolute_verification_url( |
| 35 | + request, token, user_email |
| 36 | + ) |
| 37 | + ) |
| 38 | + return link |
20 | 39 |
|
21 | 40 | # Private :
|
22 |
| - def __send_email(self, msg, useremail): |
23 |
| - subject = self.settings.get('subject') |
| 41 | + def _send_email(self, msg, useremail): |
| 42 | + subject = self.settings.get("subject") |
24 | 43 | send_mail(
|
25 |
| - subject, strip_tags(msg), |
26 |
| - from_email=self.settings.get('from_alias'), |
27 |
| - recipient_list=[useremail], html_message=msg |
| 44 | + subject, |
| 45 | + strip_tags(msg), |
| 46 | + from_email=self.settings.get("from_alias"), |
| 47 | + recipient_list=[useremail], |
| 48 | + html_message=msg, |
28 | 49 | )
|
29 | 50 |
|
30 | 51 | # Public :
|
31 |
| - def send_verification_link(self, request, inactive_user=None, form=None): |
32 |
| - |
| 52 | + @classmethod |
| 53 | + def send_verification_link(cls, inactive_user=None, form=None, request=None): |
| 54 | + self = cls() |
| 55 | + |
33 | 56 | if form:
|
34 | 57 | inactive_user = form.save(commit=False)
|
35 |
| - |
| 58 | + |
36 | 59 | inactive_user.is_active = False
|
37 | 60 | inactive_user.save()
|
38 | 61 |
|
39 | 62 | try:
|
40 |
| - |
41 |
| - useremail = form.cleaned_data.get(self.settings.get('email_field_name')) if form else inactive_user.email |
| 63 | + |
| 64 | + useremail = ( |
| 65 | + form.cleaned_data.get(self.settings.get("email_field_name")) |
| 66 | + if form |
| 67 | + else inactive_user.email |
| 68 | + ) |
42 | 69 | if not useremail:
|
43 | 70 | raise KeyError(
|
44 | 71 | 'No key named "email" in your form. Your field should be named as email in form OR set a variable'
|
45 | 72 | ' "EMAIL_FIELD_NAME" with the name of current field in settings.py if you want to use current name '
|
46 |
| - 'as email field.' |
| 73 | + "as email field." |
47 | 74 | )
|
48 | 75 |
|
49 |
| - verification_url = self.token_manager.generate_link(request, inactive_user, useremail) |
| 76 | + verification_url = self._generate_verification_url( |
| 77 | + inactive_user, useremail, request=request |
| 78 | + ) |
50 | 79 | msg = render_to_string(
|
51 |
| - self.settings.get('html_message_template', raise_exception=True), |
52 |
| - {"link": verification_url, "inactive_user": inactive_user}, |
53 |
| - request=request |
| 80 | + self.settings.get("html_message_template", raise_exception=True), |
| 81 | + {"link": verification_url, "inactive_user": inactive_user}, |
| 82 | + request=request, |
54 | 83 | )
|
55 | 84 |
|
56 |
| - self.__send_email(msg, useremail) |
| 85 | + self._send_email(msg, useremail) |
57 | 86 | return inactive_user
|
58 | 87 | except Exception:
|
59 | 88 | inactive_user.delete()
|
60 | 89 | raise
|
61 | 90 |
|
62 |
| - def resend_verification_link(self, request, email, **kwargs): |
| 91 | + @classmethod |
| 92 | + def resend_verification_link(cls, request, email, **kwargs): |
63 | 93 | """
|
64 | 94 | This method needs the previously sent link to get encoded email and token from that.
|
65 | 95 | Exceptions Raised
|
66 | 96 | -----------------
|
67 | 97 | - UserAlreadyActive (by) get_user_by_token()
|
68 | 98 | - MaxRetryExceeded (by) request_new_link()
|
69 | 99 | - InvalidTokenOrEmail
|
70 |
| - |
| 100 | +
|
71 | 101 | These exception should be handled in caller function.
|
72 | 102 | """
|
73 |
| - inactive_user = kwargs.get('user') |
74 |
| - user_encoded_token = kwargs.get('token') |
75 |
| - encoded = kwargs.get('encoded', True) |
76 |
| - |
77 |
| - if encoded: |
78 |
| - decoded_encrypted_user_token = self.token_manager.perform_decoding(user_encoded_token) |
79 |
| - email = self.token_manager.perform_decoding(email) |
80 |
| - inactive_user = self.token_manager.get_user_by_token(email, decoded_encrypted_user_token) |
81 |
| - |
82 |
| - if not inactive_user or not email: |
83 |
| - raise InvalidTokenOrEmail(f'Either token or email is invalid. user: {inactive_user}, email: {email}') |
84 |
| - |
85 |
| - # At this point, we have decoded email(if it was encoded), and inactive_user, and we can request new link |
86 |
| - link = self.token_manager.request_new_link(request, inactive_user, email) |
87 |
| - msg = render_to_string( |
88 |
| - self.settings.get('html_message_template', raise_exception=True), |
89 |
| - {"link": link}, request=request |
90 |
| - ) |
91 |
| - self.__send_email(msg, email) |
92 |
| - return True |
93 |
| - |
| 103 | + self = cls() |
94 | 104 |
|
| 105 | + try: |
| 106 | + inactive_user = kwargs.get("user") |
| 107 | + user_encoded_token = kwargs.get("token") |
| 108 | + encoded = kwargs.get("encoded", True) |
95 | 109 |
|
96 |
| -# These is supposed to be called outside of this module |
97 |
| -def send_verification_email(request, form): |
98 |
| - return _VerifyEmail().send_verification_link(request, form) |
| 110 | + if not inactive_user or not email: |
| 111 | + raise InvalidTokenOrEmail( |
| 112 | + f"Either token or email is invalid. user: {inactive_user}, email: {email}" |
| 113 | + ) |
99 | 114 |
|
| 115 | + if encoded: |
| 116 | + decoded_enc_user_token = ( |
| 117 | + self.token_manager.safe_url_encoder.perform_decoding( |
| 118 | + user_encoded_token |
| 119 | + ) |
| 120 | + ) |
| 121 | + decoded_email = self.token_manager.safe_url_encoder.perform_decoding( |
| 122 | + email |
| 123 | + ) |
| 124 | + inactive_user = self.token_manager.get_user_by_token( |
| 125 | + decoded_email, decoded_enc_user_token |
| 126 | + ) |
100 | 127 |
|
101 |
| -# These is supposed to be called outside of this module |
102 |
| -def resend_verification_email(request, email, **kwargs): |
103 |
| - return _VerifyEmail().resend_verification_link(request, email, **kwargs) |
| 128 | + # At this point, we have decoded email(if it was encoded), and inactive_user, and we can request new link |
| 129 | + new_token = self.token_manager.generate_token_for_user(inactive_user) |
| 130 | + link = self.token_manager.link_manager.request_new_link( |
| 131 | + request, inactive_user, new_token, email |
| 132 | + ) |
| 133 | + msg = render_to_string( |
| 134 | + self.settings.get("html_message_template", raise_exception=True), |
| 135 | + {"link": link}, |
| 136 | + request=request, |
| 137 | + ) |
| 138 | + self._send_email(msg, email) |
| 139 | + return True |
| 140 | + except Exception as err: |
| 141 | + logger.error( |
| 142 | + f"Error occurred during re sending the email with verification link: {err}" |
| 143 | + ) |
| 144 | + raise err |
0 commit comments