Showing a partial date#

A reader should see a partial date at the precision it was recorded at, written the way their language writes dates. A date known to the year reads as a year, and nothing suggests a month or a day that nobody gave. PartialDate.display_form() gives that text. This page shows what it gives at each precision, how to show it in a template, in Python and in the admin, and why it is a method and not what str() returns.

Get the display form of a date#

Import the value, and Django’s translation module if you need a language other than the active one.

>>> from django.utils import translation
>>> from partial_dates import PartialDate

Call display_form() on the value.

>>> with translation.override("en"):
...     PartialDate(1990).display_form()
'1990'
>>> with translation.override("en"):
...     PartialDate(1990, 6).display_form()
'June 1990'
>>> with translation.override("en"):
...     PartialDate(1990, 6, 14).display_form()
'June 14, 1990'

The method returns text for the language active when you call it, so it needs Django’s settings to be configured, which any running project has.

Show a date in a template#

A template needs the package’s app, because Django finds a tag library through an installed app. Add it to INSTALLED_APPS. The app adds no model and needs no migration.

INSTALLED_APPS = [
    # ...
    "partial_dates",
]

Load the library in the template and apply the filter.

>>> from django.template import engines
>>> template = engines["django"].from_string(
...     "{% load partial_dates %}{{ value|partial_date }}"
... )
>>> with translation.override("de"):
...     template.render({"value": PartialDate(1990, 6)})
'Juni 1990'

An empty value, and anything that is not a partial date, renders as empty text, so the page raises no error and Django’s default filter supplies whatever a missing date should show.

>>> template = engines["django"].from_string(
...     "{% load partial_dates %}{{ value|partial_date|default:'unknown' }}"
... )
>>> template.render({"value": None})
'unknown'

The text is escaped like any other text in the template. A template that shows a date with no filter, as {{ value }}, still writes the ISO form.

Show a date in the admin#

Django’s admin does not know the type, so in a list and in a read-only field it writes a partial date as its ISO form, 1990-06, whatever the language. Say once, on each admin class, that it shows the display form.

  1. Import the mixin. It is in its own module, so a project that does not use the admin never loads it.

    from partial_dates.admin import PartialDateDisplayMixin
    
  2. Put it before ModelAdmin in the bases of the admin class, and before TabularInline or StackedInline in the bases of an inline.

    from django.contrib import admin
    
    from samples.models import Note, Sample
    
    
    class NoteInline(PartialDateDisplayMixin, admin.TabularInline):
        model = Note
    
    
    @admin.register(Sample)
    class SampleAdmin(PartialDateDisplayMixin, admin.ModelAdmin):
        list_display = ["collected", "published"]
        readonly_fields = ["catalogued"]
        inlines = [NoteInline]
    
  3. Open the admin. Nothing else needs declaring, and the admin needs no entry in INSTALLED_APPS for the package.

The mixin shows the display form for every partial date field of the model:

  • in a list, where the column keeps its heading, its sorting by the stored order and its link to the row. A field in list_editable keeps its selects and saves as before.

  • in a field of readonly_fields, under the label and help text of the model field, in a default layout, in fields and in fieldsets. A TabularInline keeps the label and drops the help text, because its header looks fields up by name.

  • on every field of a page a user may only view. A field the admin leaves out, with exclude, fields or the form’s Meta.fields, stays off the page.

  • in an inline, on a read-only field and on a page a user may only view, including a user who may view the parent and change the inline.

An empty value shows the admin’s own empty display, which empty_value_display changes.

Some things keep the ISO form:

  • A field reached through a relation, as record__date in list_display, because the mixin reads the fields of the admin’s own model.

  • A method on the admin or the model that returns a partial date. It shows what it returns, so call display_form() in it.

  • An admin class with no mixin.

  • The existing rows of an inline a user may add to. They are shown as Django shows them, and the new rows keep their selects.

The add page and the change page of a user who may change the row keep the year, month and day selects for every field that is not read-only.

Show a date in another language#

Activate the language around the call. The same value reads in each language, including the name of the month.

>>> value = PartialDate(1990, 6, 14)
>>> with translation.override("de"):
...     value.display_form()
'14. Juni 1990'
>>> with translation.override("ja"):
...     value.display_form()
'1990年6月14日'

In a view, Django has already activated the language of the request, so call the method and use the text. The method works without the app entry, and it gives the same text the filter renders. The admin mixin uses the language of the request in the same way.

What each precision shows#

Precision

Format Django applies

English

German

Year

Y

1990

1990

Month

YEAR_MONTH_FORMAT

June 1990

Juni 1990

Day

DATE_FORMAT

June 14, 1990

14. Juni 1990

A site that sets its own YEAR_MONTH_FORMAT or DATE_FORMAT in a format module sees its own format here, as it does anywhere else Django writes a date. A year below 1000 has four digits at every precision, so year 990 reads 0990. The year never carries a thousands separator, whatever the site’s number settings.

Try it in the example project#

The example project in example/ turns the mixin on for its sample admin, and its list page uses the filter. After uv run python example/manage.py seed, sign in at /admin/ as view-only.user with the password password to read the samples and their notes as the display form with no selects. staff.user changes them with the selects, and a browser asking for German or Japanese reads the same dates in those languages. The example project’s setup is in the forms guide.

Why the display form is a method#

str() and isoformat() give 1990, 1990-06 and 1990-06-14 in every language. Code that stores, sorts and compares a date depends on that, so it does not change. The display form is for a reader, and it is worked out each time from the language active at that moment, so one stored value reads correctly for each person.

Why a month does not read as a day#

Each precision has a format that names only what was recorded. A month is written with the format Django has for a month and a year, which names no day, so no day is written and none is invented.