From cf1dc132fe44359fce95ddf76747c42415f0d408 Mon Sep 17 00:00:00 2001 From: Trey Piepmeier Date: Sat, 20 Jul 2024 13:35:59 -0400 Subject: [PATCH] Add per-user timezone setting The `0002` migration is to make it so it doesn't show as needing a migration if Django's list of timezones ever changes. https://dev.to/nessita/django-migrations-by-choice-32n7 --- .pre-commit-config.yaml | 1 + config/settings.py-tpl | 4 ++++ core/admin.py | 2 +- core/middleware.py | 24 ++++++++++++++++++++++++ core/models.py | 7 +++++++ dev/0002_adjust_timezone_migration.py | 21 +++++++++++++++++++++ dev/setup.sh | 1 + dev/test-build | 1 + requirements/requirements.in | 1 + requirements/requirements.txt | 2 ++ 10 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 core/middleware.py create mode 100644 dev/0002_adjust_timezone_migration.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8dfb59d..d785c1b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ repos: rev: 23.3.0 hooks: - id: black + exclude: ^core/migrations/ - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: diff --git a/config/settings.py-tpl b/config/settings.py-tpl index 18bd64f..672c84f 100644 --- a/config/settings.py-tpl +++ b/config/settings.py-tpl @@ -14,6 +14,7 @@ from django.core.management.utils import get_random_secret_key from pathlib import Path from environs import Env from warnings import filterwarnings +from pytz import common_timezones env = Env() # Read .env into os.environ @@ -72,6 +73,7 @@ MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "core.middleware.TimezoneMiddleware", "debug_toolbar.middleware.DebugToolbarMiddleware", "django_htmx.middleware.HtmxMiddleware", "django_browser_reload.middleware.BrowserReloadMiddleware", @@ -134,6 +136,8 @@ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" +TIME_ZONES = [(tz, tz) for tz in common_timezones] + USE_I18N = True USE_TZ = True diff --git a/core/admin.py b/core/admin.py index b05f6f8..05fbecf 100644 --- a/core/admin.py +++ b/core/admin.py @@ -11,7 +11,7 @@ class UserAdmin(DjangoUserAdmin): fieldsets = ( (None, {"fields": ("email", "password")}), - (_("Personal info"), {"fields": ("first_name", "last_name")}), + (_("Personal info"), {"fields": ("first_name", "last_name", "timezone")}), ( _("Permissions"), { diff --git a/core/middleware.py b/core/middleware.py new file mode 100644 index 0000000..ba02332 --- /dev/null +++ b/core/middleware.py @@ -0,0 +1,24 @@ +import pytz +from django.utils import timezone +from django.conf import settings + + +class TimezoneMiddleware: + """ + Automatically set the timezone to what the user has chosen. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if request.user.is_authenticated: + tzname = request.user.timezone + if tzname: + timezone.activate(pytz.timezone(tzname)) + else: + timezone.activate(pytz.timezone(settings.TIME_ZONE)) + else: + timezone.deactivate() + + return self.get_response(request) diff --git a/core/models.py b/core/models.py index 0177489..5878eaa 100644 --- a/core/models.py +++ b/core/models.py @@ -1,6 +1,7 @@ from django.contrib.auth.models import AbstractUser, BaseUserManager from django.db import models from django.utils.translation import gettext_lazy as _ +from django.conf import settings class UserManager(BaseUserManager): @@ -42,6 +43,12 @@ class User(AbstractUser): username = None email = models.EmailField(_("email address"), unique=True) + timezone = models.CharField( + max_length=40, + blank=True, + choices=settings.TIME_ZONES, + default=settings.TIME_ZONE, + ) # Add more fields here. objects = UserManager() diff --git a/dev/0002_adjust_timezone_migration.py b/dev/0002_adjust_timezone_migration.py new file mode 100644 index 0000000..2c95c9f --- /dev/null +++ b/dev/0002_adjust_timezone_migration.py @@ -0,0 +1,21 @@ +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0001_initial"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="timezone", + field=models.CharField( + blank=True, + choices=settings.TIME_ZONES, + default=settings.TIME_ZONE, + max_length=40, + ), + ), + ] diff --git a/dev/setup.sh b/dev/setup.sh index 67323a9..fefd8f5 100755 --- a/dev/setup.sh +++ b/dev/setup.sh @@ -168,6 +168,7 @@ gum style --border normal --margin "1" --padding "0 2" --border-foreground 212 \ "Warming up $(gum style --foreground 212 'database') and $(gum style --foreground 212 'static files')…" gum spin --title "Collecting static files" -- python manage.py collectstatic gum spin --title "Warming up the database" -- python manage.py makemigrations +gum spin --title "Warming up the database" -- mv dev/0002_adjust_timezone_migration.py core/migrations/0002_adjust_timezone_migration.py gum spin --title "Warming up the database" -- python manage.py migrate gum spin --title "Creating initial superuser account" -- python manage.py createsuperuser --noinput --email=$EMAIL diff --git a/dev/test-build b/dev/test-build index 683ef83..4452744 100755 --- a/dev/test-build +++ b/dev/test-build @@ -15,6 +15,7 @@ case $yn in .venv/bin/pip-compile --resolver=backtracking requirements/requirements.in .venv/bin/python -m pip install -r requirements/requirements.txt .venv/bin/python manage.py makemigrations + mv dev/0002_adjust_timezone_migration.py core/migrations/0002_adjust_timezone_migration.py .venv/bin/python manage.py migrate --noinput .venv/bin/python manage.py collectstatic --noinput source $HOME/.nvm/nvm.sh diff --git a/requirements/requirements.in b/requirements/requirements.in index 4829e6e..a820cf0 100644 --- a/requirements/requirements.in +++ b/requirements/requirements.in @@ -17,4 +17,5 @@ pip-lock pip-tools pre-commit pytest-django +pytz whitenoise diff --git a/requirements/requirements.txt b/requirements/requirements.txt index fc997dd..5e717bd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -157,6 +157,8 @@ pytest-django==4.8.0 # via -r requirements/requirements.in python-dotenv==1.0.1 # via environs +pytz==2024.1 + # via -r requirements/requirements.in pyyaml==6.0.1 # via # djlint