home / tils / til

Menu
  • GraphQL API

til: django_show-timezone-in-django-admin.md

This data as json

path topic title url body html shot created created_utc updated updated_utc shot_hash slug
django_show-timezone-in-django-admin.md django Show the timezone for datetimes in the Django admin https://github.com/simonw/til/blob/main/django/show-timezone-in-django-admin.md Django supports storing dates in a database as UTC but displaying them in some other timezone - which is good. But... by default datetimes are shown in the Django admin interface without any clue as to what timezone they are being displayed in. This is really confusing. A time may be stored as UTC in the database but in the admin interface it's displaying in PST, without any visual indication as to what is going on. I found a pattern today for improving this. You can use `django.conf.locale.en.formats` to specify a custom date format for a specific locale (thanks, [Stack Overflow](https://stackoverflow.com/a/32355642)). Then you can use the `e` date format option to include a string indicating the timezone that is being displayed, as [documented here](https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#date). In `settings.py` do this: ```python from django.conf.locale.en import formats as en_formats en_formats.DATETIME_FORMAT = "jS M Y fA e" ``` I added a middleware to force the displayed timezone for every page on my site to `America/Los_Angeles` like so: ```python from django.utils import timezone import pytz class TimezoneMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): timezone.activate(pytz.timezone("America/Los_Angeles")) return self.get_response(request) ``` I put this in a file called `core/timezone_middleware.py` and added it to my `MIDDLEWARE` setting in `settings.py` like so: ``` MIDDLEWARE = [ # ... "core.timezone_middleware.TimezoneMiddleware", ] ``` Now datetimes show up in my admin interface looking like this, with a `PST` suffix: <img width="593" alt="Select_report_to_change___Django_site_admin" src="https://user-images.githubusercontent.com/9599/109755937-c4fd1600-7b9b-11eb-9c65-f84bbb84ed21.png"> ## Showing UTC too I decided I'd like to see the UTC time too, just to help me truly understand what had been stored. I did that by adding the following method to my Django model class: ```python # Earlier from django.utils import dateformat import pytz # Added to the model class: def created_at_utc(self): tz = pytz.UTC created_at_utc = timezone.localtime(self.created_at, tz) return ( dateformat.format(created_at_utc, "jS M Y fA e") ) ``` Then I added `created_at_utc` to both the `list_filter` and the `readonly_fields` tuples in the admin configuration for that model. This caused it to show up in the list view and also as a read-only field at the bottom of the edit view. <img width="651" alt="Change_report___Django_site_admin" src="https://user-images.githubusercontent.com/9599/109756379-afd4b700-7b9c-11eb-8bc4-acb168c53943.png"> Note that I'm calling `dateformat.format()` in the method and returning a string - this ensures Django's automatic formatting doesn't get the chance to convert it back to PST again. <p>Django supports storing dates in a database as UTC but displaying them in some other timezone - which is good. But... by default datetimes are shown in the Django admin interface without any clue as to what timezone they are being displayed in.</p> <p>This is really confusing. A time may be stored as UTC in the database but in the admin interface it's displaying in PST, without any visual indication as to what is going on.</p> <p>I found a pattern today for improving this. You can use <code>django.conf.locale.en.formats</code> to specify a custom date format for a specific locale (thanks, <a href="https://stackoverflow.com/a/32355642" rel="nofollow">Stack Overflow</a>). Then you can use the <code>e</code> date format option to include a string indicating the timezone that is being displayed, as <a href="https://docs.djangoproject.com/en/3.1/ref/templates/builtins/#date" rel="nofollow">documented here</a>.</p> <p>In <code>settings.py</code> do this:</p> <div class="highlight highlight-source-python"><pre><span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">conf</span>.<span class="pl-s1">locale</span>.<span class="pl-s1">en</span> <span class="pl-k">import</span> <span class="pl-s1">formats</span> <span class="pl-k">as</span> <span class="pl-s1">en_formats</span> <span class="pl-s1">en_formats</span>.<span class="pl-v">DATETIME_FORMAT</span> <span class="pl-c1">=</span> <span class="pl-s">"jS M Y fA e"</span></pre></div> <p>I added a middleware to force the displayed timezone for every page on my site to <code>America/Los_Angeles</code> like so:</p> <div class="highlight highlight-source-python"><pre><span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">utils</span> <span class="pl-k">import</span> <span class="pl-s1">timezone</span> <span class="pl-k">import</span> <span class="pl-s1">pytz</span> <span class="pl-k">class</span> <span class="pl-v">TimezoneMiddleware</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">get_response</span>): <span class="pl-s1">self</span>.<span class="pl-s1">get_response</span> <span class="pl-c1">=</span> <span class="pl-s1">get_response</span> <span class="pl-k">def</span> <span class="pl-en">__call__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">request</span>): <span class="pl-s1">timezone</span>.<span class="pl-en">activate</span>(<span class="pl-s1">pytz</span>.<span class="pl-en">timezone</span>(<span class="pl-s">"America/Los_Angeles"</span>)) <span class="pl-k">return</span> <span class="pl-s1">self</span>.<span class="pl-en">get_response</span>(<span class="pl-s1">request</span>)</pre></div> <p>I put this in a file called <code>core/timezone_middleware.py</code> and added it to my <code>MIDDLEWARE</code> setting in <code>settings.py</code> like so:</p> <pre><code>MIDDLEWARE = [ # ... "core.timezone_middleware.TimezoneMiddleware", ] </code></pre> <p>Now datetimes show up in my admin interface looking like this, with a <code>PST</code> suffix:</p> <p><a href="https://user-images.githubusercontent.com/9599/109755937-c4fd1600-7b9b-11eb-9c65-f84bbb84ed21.png" target="_blank" rel="nofollow"><img width="593" alt="Select_report_to_change___Django_site_admin" src="https://user-images.githubusercontent.com/9599/109755937-c4fd1600-7b9b-11eb-9c65-f84bbb84ed21.png" style="max-width:100%;"></a></p> <h2> <a id="user-content-showing-utc-too" class="anchor" href="#showing-utc-too" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Showing UTC too</h2> <p>I decided I'd like to see the UTC time too, just to help me truly understand what had been stored. I did that by adding the following method to my Django model class:</p> <div class="highlight highlight-source-python"><pre><span class="pl-c"># Earlier</span> <span class="pl-k">from</span> <span class="pl-s1">django</span>.<span class="pl-s1">utils</span> <span class="pl-k">import</span> <span class="pl-s1">dateformat</span> <span class="pl-k">import</span> <span class="pl-s1">pytz</span> <span class="pl-c"># Added to the model class:</span> <span class="pl-k">def</span> <span class="pl-en">created_at_utc</span>(<span class="pl-s1">self</span>): <span class="pl-s1">tz</span> <span class="pl-c1">=</span> <span class="pl-s1">pytz</span>.<span class="pl-v">UTC</span> <span class="pl-s1">created_at_utc</span> <span class="pl-c1">=</span> <span class="pl-s1">timezone</span>.<span class="pl-en">localtime</span>(<span class="pl-s1">self</span>.<span class="pl-s1">created_at</span>, <span class="pl-s1">tz</span>) <span class="pl-k">return</span> ( <span class="pl-s1">dateformat</span>.<span class="pl-en">format</span>(<span class="pl-s1">created_at_utc</span>, <span class="pl-s">"jS M Y fA e"</span>) )</pre></div> <p>Then I added <code>created_at_utc</code> to both the <code>list_filter</code> and the <code>readonly_fields</code> tuples in the admin configuration for that model. This caused it to show up in the list view and also as a read-only field at the bottom of the edit view.</p> <p><a href="https://user-images.githubusercontent.com/9599/109756379-afd4b700-7b9c-11eb-8bc4-acb168c53943.png" target="_blank" rel="nofollow"><img width="651" alt="Change_report___Django_site_admin" src="https://user-images.githubusercontent.com/9599/109756379-afd4b700-7b9c-11eb-8bc4-acb168c53943.png" style="max-width:100%;"></a></p> <p>Note that I'm calling <code>dateformat.format()</code> in the method and returning a string - this ensures Django's automatic formatting doesn't get the chance to convert it back to PST again.</p> <Binary: 83,093 bytes> 2021-03-02T21:17:45-08:00 2021-03-03T05:17:45+00:00 2021-03-02T21:17:45-08:00 2021-03-03T05:17:45+00:00 76244473ec3e74804200af70034ecce8 show-timezone-in-django-admin
Powered by Datasette · How this site works · Code of conduct