Search code examples
djangodjango-sessionsdjango-middlewaredjango-timezone

What is the best way to make sure the "django-timezone" session variable is set based on a user's profile preference each time they log in


Background

I read this section in Django's documentation titled "Selecting the current time zone,", and it's clear to me how a user can select their preferred timezone for their session (and then the middleware makes sure it is used throughout their session):

  • developer prepares a dictionary of timezones for users to select from
  • developer makes this list available to user in a form within a template that is served by a GET request to a view called set_timezone
  • user selects timezone using this form and makes a POST request
  • set_timezone view uses a simple one-liner to set session "django-timezone" variable: request.session["django_timezone"] = request.POST["timezone"]
  • Finally, the custom TimezoneMiddleware class referenced in the documentation will then activate the timezone for all view requests in the session from that point on

Question

If I store a user's timezone preference in a User model or User profile model, what is the best way to make sure the "django-timezone" session variable is always set for all their sessions (each time they log in)?

Thoughts/guesses

  • Would I need to modify a built-in Django authentication middleware class of some sort?
  • Would it be best to extend the Django-standard LoginView class from django.contrib.auth.views?
  • Or will the aforementioned LoginView class already handle this on its own if I set up a timezone storing field in my User model correctly? (or something like this?)

Solution

  • Extending LoginView

    As suggested by Kevin Christopher Henry in the comments, I extended the built-in login view (decided to put the logic in the form_valid() method) after implementing what was suggested in Django's documentation titled "Selecting the current time zone." I'll show the relevant parts of my custom user model as well so it's clear how I'm storing users' time zone preferences.

    models.py

    from django.contrib.auth.base_user import AbstractBaseUser
    from django.contrib.auth.models import PermissionsMixin
    from django.contrib.auth.validators import ASCIIUsernameValidator
    # third-party package
    from timezone_field import TimeZoneField
    
    TZ_CHOICES = [
        (ZoneInfo('Pacific/Honolulu'), 'Pacific/Honolulu'),
        (ZoneInfo('America/Anchorage'), 'America/Anchorage'),
        (ZoneInfo('America/Los_Angeles'), 'America/Los_Angeles'),
        (ZoneInfo('US/Arizona'), 'US/Arizona'),
        (ZoneInfo('America/Denver'), 'America/Denver'),
        (ZoneInfo('America/Chicago'), 'America/Chicago'),
        (ZoneInfo('America/New_York'), 'America/New_York'),
    ]
    
    class CustomUser(AbstractBaseUser, PermissionsMixin):
        username_validator = ASCIIUsernameValidator()
    
        """A bunch of fields (username, email, etc.)"""
    
        tz_preference = TimeZoneField(
            use_pytz=False, default="America/Denver", choices=TZ_CHOICES, choices_display="STANDARD", verbose_name=_('Timezone Preference')
        )
    

    views.py

    from django.contrib.auth.views import LoginView
    from django.contrib.auth import login as auth_login
    import zoneinfo
    from django.utils import timezone
    
    class CustomLoginView(LoginView):
        def form_valid(self, form):
            """Security check complete. Log the user in."""
            auth_login(self.request, form.get_user())
            # now set session timezone according to user's preference
            tzname = self.request.user.tz_preference.key
            self.request.session["django_timezone"] = tzname
            # and activate it (after this view, middleware will activate it)
            timezone.activate(zoneinfo.ZoneInfo(tzname))
            return HttpResponseRedirect(self.get_success_url())