Using Timezones in Django

Making Timezone Aware Applications heyylateef | Sep 16 2021

Many of the applications you use every day are timezone sensitive. Facebook, YouTube, Instagram, and even LateefLab are all timezone sensitive web application. 3pm in Washington D.C. is actually 12pm in Los Angeles. Django supports pytz, so we can use the module to make our application timezone aware. We're going to create a simple blog app that'll demonstrate the capabilities of Django's timezone support.


In this tutorial we're going to:

  1. Create Django project
  2. Create models.py and edit admin.py
  3. Creating middleware.py file
  4. Setting up the views.py and urls.py
  5. Creating a template with timezone aware date objects
  6. Run migrations, create superuser, and make a blog post



1. Create Django Project



You'll need to create a folder for our project (Note: Use a virtual environment such as venv or conda. This will separate). Create a folder on your desktop, call it djangoTimezoneTutorial. In terminal/CMD, enter the following:

$ cd djangoTimezoneTutorial #changes directory
$ django-admin startproject tzTutorial #creates a directory titled "tzTutorial". This is your Django project

 Once the Django project has been created, open the folder titled tzTutorial. There will be a file called settings.py. Open it, and scroll down until you find a variable named USE_TZ. Make sure the value is set to "True".



2. Create a model

To demonstrate dynamic date and time objects in Django, we need to create a model with datetime aware objects. Let's create a Django app for our project.

In terminal/CMD, enter the following:

$ python manage.py startapp tzapp

Open up the tzapp folder, open the file named models.py. Copy the following models.py:


class Blogpost(models.Model):

title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.ForeignKey(User, on_delete= models.CASCADE,related_name='blog_posts')
content = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.title

Inside the same folder, open admin.py. Later, we'll use the built-in Django admin page to submit our blog post. Copy and paste the following to your admin.py file:

from django.contrib import admin
from .models import Blogpost

class BlogpostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug','created_on')
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}

admin.site.register(Blogpost, BlogpostAdmin)


3. Create middleware.py

Inside the tzapp folder, create a new file named middleware.py. This file will hold the logic that takes care of the timezone activation for our website. Copy the following into middleware.py:

import pytz

from django.utils import timezone

class TimezoneMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
tzname = request.session.get('django_timezone')
if tzname:
timezone.activate(pytz.timezone(tzname))
else:
timezone.deactivate()
return self.get_response(request)



Also, lets install the app we created inside settings.py. Under installed apps, add tzapp and activate our app's middleware.py, just like the following:

INSTALLED_APPS = [
'lateeflabapp.apps.LateeflabappConfig',
'blogapp.apps.BlogappConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
#'cart', #Django-cart,
'django_summernote', #WYSIWYG Editor
'django.contrib.sites', # Needed only to use sitemap framework
'django.contrib.sitemaps', #Sitemap framework; good for SEO
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'blogapp.middleware.TimezoneMiddleware', #custom middleware, for timezone support

]



4. Create a View

Inside our tzapp folder, open views.py. We need to create a view for our blog. Copy the following:

from django.shortcuts import render, redirect, reverse
from django.views import generic
from .models import Blogpost
from django.contrib import messages
import pytz


def home(request):
    #If the browser's timezone isn't set, ask user to set timezone
if request.session.get('django_timezone') == None:
return render(request, 'tzapp/settimezone.html', {'timezones': pytz.common_timezones})
else:
queryset = Blogpost.objects.all()
return render(request, 'tzapp/home.html', {'post':queryset})

def settimezone(request):
if request.method == 'POST':
    #Sets the request session's timezone to the user selected timezone,
    # so all datetime objects will be rendered in the user's desired timezone
request.session['django_timezone'] = request.POST['timezone']
return redirect(reverse("home"))
else:
return render(request, 'tzapp/settimezone.html', {'timezones': pytz.common_timezones})
return render(request, 'tzapp/settimezone.html', {'timezones': pytz.common_timezones})

class PostDetail(generic.DetailView):
model = Blogpost
template_name = 'tzapp/post_detail.html'

There is a good amount of things going on in our views.py. First, the "home" view, which is intended to our landing page, has a condition. If the timezone isn't set in the user's session, Django will redirect our user to the timezone selection template. Otherwise, the user will continue on to our landing page.

Second, we have our settimezone view. This function will make an HTTP Post request to our server, sending the name of the selected timezone back to the server and sets the user session's django_timezone variable.

Lastly, we create a class based view named "PostDetail". Using a class based view is different than a function based view and has many advantages in comparison to function based views. However, this tutorial won't go into the differences between the two. The purpose of this class based function is to utilize it's detail view features. Using the DetailView, we can create a basic view with only defining the HTML template and a model.


Now, we need to setup the urls.py for both our project and app. Inside the project folder (tzTutiorial), open urls.py. Copy and paste the following:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('tzapp.urls')), # urls for our app
]

Next, we need to setup the urls.py for our app. Open our app folder (tzapp) and create a new file, name it urls.py. Add the following code to our app's urls.py:

from django.urls import path
from . import views

urlpatterns = [
path('',views.home, name='home'),
path('<slug:slug>/', views.PostDetail.as_view(), name='post_detail'),
path('settimezone', views.settimezone, name='settimezone')

]



5. Templates

Each view/url for our app needs a corresponding template, so lets create them. First, lets create the correct folder structure within our project's app folder. Inside of our app (tzapp) folder, create a folder named "templates" Inside of that folder, create another folder named tzapp (Note: the name of this folder needs to be the same name as our app). This is the folder that'll contain all of our HTML templates.

First, lets create our base.html. Django developers like to create a base.html as we use it to hold all the site-wide header HTML code, such as stylesheets and JavaScript source files. In the newly created folder, create a file named "base.html" Add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
{% load static %}
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/
              bootstrap.min.css">

<!-- Font Awesome (free icons) -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css"
            integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ"
            crossorigin="anonymous">


<title>{% block title %}LateefLab TzTutorial{% endblock title %}</title>
</head>
<body>

</style>
<header>
{# Navigation Menu #}
</header>
{# The Document Body #}
<div class="container">
{% block content %}
if you see this, something is wrong!
{% endblock content %}
</div>
{# The Footer #}
<div class="footer">
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
    </script>
<!-- Popper JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js">
    </script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js">
    </script>

</div>
</body>
</html>


Next, we'll add in the landing page that'll contain the front facing HTML. Inside the same folder you created base.html, create a new file named home.html. Copy and paste the following code in your text editor:

{% extends "tzapp/base.html" %}
{% block content %}


<div class="container">
<div class="row">
<!-- Blog Entries Column -->
<div class="col-md-10 col-lg-8 col-xl-7">
<!-- Post preview-->
{% for posts in post %}
<div class="post-preview">
<a href="{% url 'post_detail' posts.slug %}">
<h2 class="post-title">{{ posts.title }}</h2>
<h3 class="post-subtitle">{{posts.subheading|slice:":100"|safe }}</h3>
</a>
<p class="post-meta">{{ posts.author }} | {{ posts.created_on}}</p>
</div>
<!-- Divider-->
<hr class="my-4" />
{% endfor %}
</div>
</div>
</div>
{%endblock%}


Now we'll create another file named post_detail.html. This template will utilize the power of Django's class based view (the one we created earlier). Combined with the url we defined in tzapp/urls.py on line 6, we can display all the attributes of  our blogpost model in a more streamlined in comparison to using a function based view! Copy and paste the following code in post_detail.html:

{% extends "tzapp/base.html" %}
{% block content %}


<!-- Page Header-->
<header class="masthead">
<div class="container h-100">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1 class="text-white font-weight-bold">
                        {% block title %} {{ object.title }} {% endblock title %}</h1>
<span class="subheading">{{ object.author }} | {{ object.created_on }}</span>
</div>
</div>
</div>
</div>
</div>
</header>

<div class="container pt-5" >
<div class="row">
<div class="col card mb-4 mt-3 left top">
<div class="card-body">
<p></p>
<p >{{ object.content|safe }}</p>
</div>
</div>
</div>
</div>

{% endblock content %}


Last but definitely not least, create another file named settimezone.html inside the templates folder. This is the template that a user will interact with to set their timezone (if they haven't done so). Copy and past the following code for settimezone.html:

{# HTML5 declaration #}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/
          bootstrap.min.css">

<!-- Font Awesome (free icons) -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css"
            integrity="sha384-lZN37f5QGtY3VHgisS14W3ExzMWZxybE1SJSEsQp9S+oqd12jhcu+A56Ebc1zFSJ"
            crossorigin="anonymous">

<title>{% block title %}Pick Your Timezone{% endblock title %}</title>
</head>
<body>
<header>
{# Navigation Menu #}
</header>
{# The Document Body #}

<div class="container-fluid p-0">
{% block content %}
<!--if you see this, something is wrong!-->
{% load tz %}
{% get_current_timezone as TIME_ZONE %}
<div class="container" style="text-align: center; padding-top: 10%;">
<h1 class= "pb-5">Set Your Timezone</h1>
<div class="row pt-5 pb-5 justify-content-center">
<form action="{% url 'settimezone' %}" method="POST" class="form-inline">
{% csrf_token %}
<label for="timezone">Time zone:</label>
<select name="timezone" class="form-control mr-3 ">
{% for tz in timezones %}
<option value="{{ tz }}"{% if tz == TIME_ZONE %}
                                    selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
<input type="submit" value="Set" class="form-control btn btn-success">
</form>
</div>
</div>
{% endblock content %}
</div>
{# The Footer #}
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"><
        /script>
<!-- Popper JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js">
        </script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js">
        </script>
</body>
</html>

6. Run migrations, create superuser, and make a blog post!

We're nearly finished with setting up the project! It'll be a good idea to run migrations before we create a superuser and run the project. Inside of your terminal/CMD, run the following commands from your project's root folder (tzTutorial; same folder level as settings.py):

$ python manage.py makemigrations
$ python manage.py migrate

While we're still in the terminal/CMD, lets create a super user. Run the following command in your terminal/CMD:

$ python manage.py createsuperuser

Follow the prompt, providing a username, email address, and password. Once finished, now lets run the project!

In your terminal/CMD, run the following command to start the project:

$ python manage.py runserver


Go to http://localhost:8000 , you'll be greeted by the settimezone.html!:


Once you set your timezone, you'll see an error such as this:


We get this error because we haven't created any blogposts (also this tutorial doesn't handle this particular error of no blogposts). Lets create a blogpost! Go to http://localhost:8000/admin and sign in with the same superuser credentials you did earlier.

Once signed in, click "Blogposts". Now click "Add Blogpost" button. Fill out the model (don't forget to select yourself as the author).

After you submitted, go back to http://localhost:8000.You should see the post you just created!


To demonstrate the timezone, lets delete our cookies so the next time we go to the homepage, it'll ask us to set our timezone. I'm in the US/Eastern (UTC-5) timezone, but i'll switch to US/West(UTC-8). So when I reset my timezone, it should show 8:40pm.



And there you have it. Now you're setting up timezone support in your Django projects! Check out the Github repo: tzTutorial Repo

About The Lab

Like the content posted on this site? Need help with another web project? Want to give some feedback? Feel free to contact me via email or social media!

Know more!
DigitalOcean Referral Badge