|
| 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