Considerations for Updating Time Zones in Django
Background
In the previous article, How to Use Time Zones with Django, we look at how to structure our Django project to accommodate users in different time zones (If you haven't already gone through this article I recommend taking a look before proceeding given the discussion here builds upon it). We extend the user model to accommodate a time zone field, build a little middleware to check if the time zone is set and then subsequently set it if not, and then we talk about how we should be working with date-time objects so they're time zone aware.
The example covers a lot of ground but leaves out a particular situation for purposes of brevity: What if the user updates their time zone setting in the middle of a session? The answer is: nothing. The change won't take effect. When the middleware checks to see if a session cookie has been set with the appropriate time zone, it will conclude that a value has been stored in a session cookie and will query the database.
And that's by design. We don't want to constantly query the database with every request. We instead prefer data retrieved often, such as time zones, to be cached for efficiency and better user experience.
This should point to where then answer lies. The session is the problem because it doesn't have the most accurate information. So then let's flush the session? And there's a term for that: logging out. If we want to see our new time zone selection take effect we could simply log out and log back in again and voila, are change will take effect. With the old session flushed, a new session is created on the fresh log in. On the very first request the middleware will determine that we have not yet set the session cookie for time zone and thus query the database and retrieving the new value.
But logging out and logging back in to refresh for changes isn't too intuitive in my opinion. When a user commits a change, they expect that change to be reflected immediately. Going through the effort of starting a new session seems like a work around and not a complete solution. That said, how often will your users be changing their time zone preferences? Maybe not too often but they're probably most likely to make a change like this near the beginning of their use with your project. A fresh user probably isn't someone you'd like to frustrate so let's instead be complete.
So then, what's the fix? How do we make sure our session has stored the most up-to-date information? First, I'll mention what it's not. We don't want to tie into the data mod with a signal and we don't want to extend the model's save method either. Neither of these approaches will have the request object available to us and we need the request object in order to update the session value.
That should tell you where we should add our logic: the view.
The Fix
We want to update the time zone value stored in the session cookie in the view because there we have the request object available to us. More specifically, we want to override the view's form_valid method since we only want to commit the change if the form we receive is valid. Furthermore, we'll check to make sure our field is present in the form's changed_data attribute. If the value was not altered by the user, then we don't want to waste an operation to replace a value with the same value.
Putting this together will look something like:
# views.py
from django.views.generic import UpdateView
# local imports here
class ProfileUpdateView(UpdateView):
...
def form_valid(self, form):
if 'timezone' in form.changed_data:
_ = self.request.session.pop('django_timezone', None)
return super().form_valid(form)
Note, we don't have to activate our time zone here. All we need to do is flush the session cookie's stored value, if any. Then when our view redirects us to our success URL with a fresh request, the middleware we built in the previous article will find our session value isn't available. It will then retrieve the updated value from the database and restore the session cookie with the new value.
That said, we do have the value available to us in our form_valid method so why not set our session cookie with the new value and save ourselves the database call that our middleware would otherwise make on our next request? That will look like:
# views.py
from django.views.generic import UpdateView
# local imports here
class ProfileUpdateView(UpdateView):
...
def form_valid(self, form):
if 'timezone' in form.changed_data:
self.request.session['django_timezone'] = form.cleaned_data.get('timezone')
return super().form_valid(form)
And there you have it. Now our users can expect their changes to take effect immediately.
This example was produced using class based views. If you're using function views then the approach is very similar. The only difference is you need to instantiate your form with your post data and then call form.is_valid() so that the changed_data attribute is available to you.
Final Thoughts
We want our users to have a seamless experience. While having your users log out and log back so their changes take effect is a technically functional solution, it's not very intuitive and it forces the user to accommodate the designer or developer. Rather, I've demonstrated a quick change to your view that updates user profile information to allow for changes to their time zone preferences to be reflected immediately.