Skip to content

Commit 154812d

Browse files
committed
massive code refactored - UNTESTED
1 parent cd9e1a6 commit 154812d

File tree

12 files changed

+653
-334
lines changed

12 files changed

+653
-334
lines changed

verify_email/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
from .email_handler import *
2-
3-

verify_email/app_configurations.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from dataclasses import dataclass
22

33
from django.conf import settings
4-
from verify_email.interface import DefaultConfig
4+
from .interface import DefaultConfig
55

66

77
@dataclass
@@ -23,7 +23,7 @@ class GetFieldFromSettings:
2323

2424
def __post_init__(self):
2525
self.defaults_configs = {
26-
"debug_settings": DefaultConfig(setting_field="DEBUG", default_value=False),
26+
"debug": DefaultConfig(setting_field="DEBUG", default_value=False),
2727
"subject": DefaultConfig(
2828
setting_field="SUBJECT", default_value="Email Verification Mail"
2929
),
@@ -83,7 +83,9 @@ def get(self, field_name, raise_exception=True, default_type=str):
8383
attr = getattr(
8484
settings,
8585
self.defaults_configs[field_name].setting_field, # get field from settings
86-
self.defaults_configs[field_name].default_value, # get default value if field not defined
86+
self.defaults_configs[
87+
field_name
88+
].default_value, # get default value if field not defined
8789
)
8890
if not attr and not isinstance(field_name, default_type) and raise_exception:
8991
if field_name == "verification_success_template" and attr is None:

verify_email/apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77

88
class VerifyEmailConfig(AppConfig):
9-
name = 'verify_email'
9+
name = "verify_email"
1010

1111
def ready(self):
12-
logger.info('[Email Verification] : importing signals - OK.')
12+
logger.info("[Email Verification] : importing signals - OK.")
1313
import verify_email.signals

verify_email/confirm.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
from dataclasses import dataclass
33

44
from .token_manager import TokenManager
5+
from .custom_types import User
56
from django.utils import timezone
67

8+
79
logger = logging.getLogger(__name__)
810

911

@@ -36,7 +38,7 @@ class UserActivationProcess:
3638
token_manager: TokenManager = TokenManager()
3739

3840
@classmethod
39-
def activate_user(cls, encoded_email: str, encoded_token: str) -> None:
41+
def activate_user(cls, encoded_email: str, encoded_token: str) -> User:
4042
"""
4143
Steps to activate a user account:
4244
1. Verify the token.
@@ -74,6 +76,7 @@ def activate_user(cls, encoded_email: str, encoded_token: str) -> None:
7476
self = cls() # Consider if instantiation is necessary here
7577
try:
7678
# Verify token and retrieve user object
79+
# this will return inactive user
7780
user = self.token_manager.decrypt_token_and_get_user(
7881
encoded_email, encoded_token
7982
)
@@ -82,6 +85,7 @@ def activate_user(cls, encoded_email: str, encoded_token: str) -> None:
8285
user.is_active = True
8386
user.last_login = timezone.now()
8487
user.save()
88+
return user
8589
except Exception as err:
8690
logger.exception(err)
8791
# Perform any necessary cleanup if required

verify_email/custom_types.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from typing import TypeVar
2+
3+
from django.contrib.auth import get_user_model
4+
5+
User = TypeVar("User", bound=get_user_model())

verify_email/email_handler.py

Lines changed: 90 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,144 @@
1+
from dataclasses import dataclass
2+
import logging
3+
14
from django.core.mail import send_mail
25
from django.template.loader import render_to_string
36
from django.utils.html import strip_tags
47

58
from .app_configurations import GetFieldFromSettings
69
from .errors import InvalidTokenOrEmail
710
from .token_manager import TokenManager
11+
from .custom_types import User
12+
13+
logger = logging.getLogger(__name__)
814

915

10-
class _VerifyEmail:
16+
@dataclass(frozen=True)
17+
class ActivationMailManager:
1118
"""
1219
This class does two things:
1320
1. set each user as inactive and saves it
1421
2. sends the email to user with that link.
1522
"""
1623

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
2039

2140
# 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")
2443
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,
2849
)
2950

3051
# 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+
3356
if form:
3457
inactive_user = form.save(commit=False)
35-
58+
3659
inactive_user.is_active = False
3760
inactive_user.save()
3861

3962
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+
)
4269
if not useremail:
4370
raise KeyError(
4471
'No key named "email" in your form. Your field should be named as email in form OR set a variable'
4572
' "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."
4774
)
4875

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+
)
5079
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,
5483
)
5584

56-
self.__send_email(msg, useremail)
85+
self._send_email(msg, useremail)
5786
return inactive_user
5887
except Exception:
5988
inactive_user.delete()
6089
raise
6190

62-
def resend_verification_link(self, request, email, **kwargs):
91+
@classmethod
92+
def resend_verification_link(cls, request, email, **kwargs):
6393
"""
6494
This method needs the previously sent link to get encoded email and token from that.
6595
Exceptions Raised
6696
-----------------
6797
- UserAlreadyActive (by) get_user_by_token()
6898
- MaxRetryExceeded (by) request_new_link()
6999
- InvalidTokenOrEmail
70-
100+
71101
These exception should be handled in caller function.
72102
"""
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()
94104

105+
try:
106+
inactive_user = kwargs.get("user")
107+
user_encoded_token = kwargs.get("token")
108+
encoded = kwargs.get("encoded", True)
95109

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+
)
99114

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+
)
100127

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

verify_email/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ def __init__(self, *args: object) -> None:
2626
class WrongTimeInterval(Exception):
2727
def __init__(self, *args: object) -> None:
2828
super().__init__(*args)
29+
30+
31+
class DecodingFailed(Exception):
32+
def __init__(self, *args: object) -> None:
33+
super().__init__(*args)

verify_email/interface.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
@dataclass
66
class DefaultConfig:
77
"""Default configuration for the application."""
8+
89
setting_field: str
910
default_value: Union[str, Any]

verify_email/migrations/0001_initial.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,25 @@ class Migration(migrations.Migration):
1515

1616
operations = [
1717
migrations.CreateModel(
18-
name='LinkCounter',
18+
name="LinkCounter",
1919
fields=[
20-
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21-
('sent_count', models.IntegerField()),
22-
('requester', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
20+
(
21+
"id",
22+
models.BigAutoField(
23+
auto_created=True,
24+
primary_key=True,
25+
serialize=False,
26+
verbose_name="ID",
27+
),
28+
),
29+
("sent_count", models.IntegerField()),
30+
(
31+
"requester",
32+
models.OneToOneField(
33+
on_delete=django.db.models.deletion.CASCADE,
34+
to=settings.AUTH_USER_MODEL,
35+
),
36+
),
2337
],
2438
),
2539
]

0 commit comments

Comments
 (0)