Skip to content

Commit ebfea57

Browse files
committed
Save user timezone to session and use that to get the local time in the login notification e-mail.
1 parent ca671ef commit ebfea57

5 files changed

Lines changed: 46 additions & 1 deletion

File tree

hypha/apply/users/signals.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from hypha.core.mail import MarkdownMail
99

10+
from .utils import get_zoneinfo
11+
1012
HIJACK_VIEW_NAMES = {
1113
"hijack-become",
1214
"users:hijack",
@@ -20,10 +22,18 @@ def send_login_notification(sender, request, user, **kwargs):
2022
if not settings.SEND_MESSAGES or not user.email:
2123
return
2224

25+
if getattr(user, "backend", "").startswith("social_core."):
26+
return
27+
2328
if request and getattr(request, "resolver_match", None):
2429
if request.resolver_match.view_name in HIJACK_VIEW_NAMES:
2530
return
2631

32+
tz_name = (
33+
getattr(request, "session", {}).get("user_timezone", "") if request else ""
34+
)
35+
user_tz = get_zoneinfo(tz_name)
36+
2737
subject = _("Successful login to %(org)s") % {"org": settings.ORG_LONG_NAME}
2838
if settings.EMAIL_SUBJECT_PREFIX:
2939
subject = str(settings.EMAIL_SUBJECT_PREFIX) + str(subject)
@@ -35,7 +45,9 @@ def send_login_notification(sender, request, user, **kwargs):
3545
from_email=settings.DEFAULT_FROM_EMAIL,
3646
context={
3747
"user": user,
38-
"login_time": formats.date_format(timezone.now(), "SHORT_DATETIME_FORMAT"),
48+
"login_time": formats.date_format(
49+
timezone.localtime(timezone=user_tz), "SHORT_DATETIME_FORMAT"
50+
),
3951
"site": Site.find_for_request(request) if request else None,
4052
"ORG_EMAIL": settings.ORG_EMAIL,
4153
},

hypha/apply/users/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
oauth,
3838
send_confirm_access_email_view,
3939
set_password_view,
40+
set_timezone_view,
4041
)
4142

4243
app_name = "users"
@@ -159,6 +160,7 @@
159160
),
160161
path("activate/", create_password, name="activate_password"),
161162
path("oauth", oauth, name="oauth"),
163+
path("set-timezone/", set_timezone_view, name="set_timezone"),
162164
# 2FA
163165
path("two_factor/setup/", TWOFASetupView.as_view(), name="setup"),
164166
path(

hypha/apply/users/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import string
2+
import zoneinfo
23

34
import nh3
45
from django.conf import settings
@@ -196,6 +197,16 @@ def update_is_staff(request, user):
196197
user.save()
197198

198199

200+
def get_zoneinfo(tz_name):
201+
"""Return a ZoneInfo for tz_name, or None if invalid/empty."""
202+
if not tz_name:
203+
return None
204+
try:
205+
return zoneinfo.ZoneInfo(tz_name)
206+
except (zoneinfo.ZoneInfoNotFoundError, KeyError):
207+
return None
208+
209+
199210
def strip_html_and_nerf_urls(value: str):
200211
# Remove all HTML tags. This prohibits HTML without creating hurdles.
201212
cleaned_value = nh3.clean(value, tags=set())

hypha/apply/users/views.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from django.views.decorators.cache import never_cache
3232
from django.views.decorators.csrf import csrf_exempt
3333
from django.views.decorators.debug import sensitive_post_parameters
34+
from django.views.decorators.http import require_POST
3435
from django.views.generic import UpdateView
3536
from django.views.generic.base import TemplateView
3637
from django.views.generic.edit import FormView
@@ -70,6 +71,7 @@
7071
from .utils import (
7172
generate_numeric_token,
7273
get_redirect_url,
74+
get_zoneinfo,
7375
send_activation_email,
7476
send_confirmation_email,
7577
)
@@ -870,6 +872,15 @@ def set_password_view(request):
870872
return HttpResponse(_("✓ Check your email for password set link."))
871873

872874

875+
@require_POST
876+
def set_timezone_view(request: HttpRequest) -> HttpResponse:
877+
"""Store the browser timezone in the session for use in login notifications."""
878+
tz_name = request.POST.get("user_timezone", "")
879+
if get_zoneinfo(tz_name):
880+
request.session["user_timezone"] = tz_name
881+
return HttpResponse(status=204)
882+
883+
873884
@never_cache
874885
@csrf_exempt
875886
@psa(f"{settings.SOCIAL_AUTH_URL_NAMESPACE}:complete")

hypha/templates/base.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@
202202
});
203203
</script>
204204
{% endif %}
205+
{% if not request.session.user_timezone %}
206+
<script>
207+
fetch("{% url 'users:set_timezone' %}", {
208+
method: 'POST',
209+
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': '{{ csrf_token }}'},
210+
body: 'user_timezone=' + encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone)
211+
});
212+
</script>
213+
{% endif %}
205214
{% include "includes/body_end.html" %}
206215
</body>
207216
</html>

0 commit comments

Comments
 (0)