Django REST framework#
You expose a partial date through an API with PartialDateField, a serializer field for Django
REST framework. A client reads the date as its ISO form, 1990, 1990-06 or 1990-06-14, or as
null when a record has none, and writes the same text. Anything else is reported as an ordinary
validation error with the code invalid. The field lives in partial_dates.rest_framework, and
the core package never imports Django REST framework.
Set up the examples#
The examples use these models, the ones from the querying guide and the installation page, with one more whose date is required.
from django.db import models
from partial_dates import PartialDateField
class Sample(models.Model):
collected = PartialDateField(null=True, blank=True, db_index=True)
class Event(models.Model):
occurred = PartialDateField(unique=True)
Every example imports what it uses from these lines.
>>> from types import SimpleNamespace
>>> from rest_framework import serializers
>>> from partial_dates import PartialDate
>>> from partial_dates.rest_framework import PartialDateField
Install the extra#
Install the package with the djangorestframework extra, in the environment your project runs in.
$ pip install "django-partial-dates[djangorestframework]"
The extra adds Django REST framework and nothing else. Your project needs no entry in
INSTALLED_APPS for this field. Installing it does not install django-filter, and the field never
imports it.
Declare the field#
Declare PartialDateField on a serializer as you would any other field.
>>> class SampleSerializer(serializers.Serializer):
... collected = PartialDateField()
The field takes the options every Django REST framework field takes, such as required,
read_only, source, allow_null and validators, and they behave as they do on any other
field.
Read a date#
Give the serializer an object and read data. A date is written at the precision it was
recorded at, with nothing added, and a record with no date is written as None, which a JSON
renderer turns into null.
>>> SampleSerializer(SimpleNamespace(collected=PartialDate(1990))).data["collected"]
'1990'
>>> SampleSerializer(SimpleNamespace(collected=PartialDate(1990, 6))).data["collected"]
'1990-06'
>>> SampleSerializer(SimpleNamespace(collected=PartialDate(1990, 6, 14))).data["collected"]
'1990-06-14'
>>> SampleSerializer(SimpleNamespace(collected=None)).data["collected"] is None
True
An object that still holds ISO text, such as a model instance you built in Python and have not read back from the database, is written as that same text.
>>> SampleSerializer(SimpleNamespace(collected="1990-06")).data["collected"]
'1990-06'
Write a date#
Give the serializer incoming data and call is_valid(). Text in one of the three ISO forms, or a
PartialDate, is validated to the PartialDate it names.
>>> serializer = SampleSerializer(data={"collected": "1990-06"})
>>> serializer.is_valid()
True
>>> serializer.validated_data["collected"]
PartialDate(year=1990, month=6, day=None)
A ModelSerializer that declares the field saves the value.
>>> class SampleModelSerializer(serializers.ModelSerializer):
... collected = PartialDateField(allow_null=True)
...
... class Meta:
... model = Sample
... fields = ["collected"]
>>> serializer = SampleModelSerializer(data={"collected": "1990-06-14"})
>>> serializer.is_valid()
True
>>> serializer.save().collected
PartialDate(year=1990, month=6, day=14)
Accept no date#
Declare the field with allow_null=True and a client may send null. The validated value is
None. Without it, Django REST framework reports its own null error. A form posted to the API
with an empty entry gives None on a field that allows null, as it does on any other field.
>>> serializer = SampleModelSerializer(data={"collected": None})
>>> serializer.is_valid()
True
>>> serializer.validated_data["collected"] is None
True
>>> serializer = SampleSerializer(data={"collected": None})
>>> serializer.is_valid()
False
>>> serializer.errors["collected"][0].code
'null'
Handle a refused entry#
An entry that is not a partial date is reported as an error on the field, with the code
invalid, and nothing is written. That covers text such as 1990-6 or 1990-02-30, an empty
string, the number 1990, a list, an object and true.
>>> for entry in ["1990-6", "1990-02-30", "", 1990, [], {}, True]:
... serializer = SampleSerializer(data={"collected": entry})
... print(serializer.is_valid(), serializer.errors["collected"][0].code)
False invalid
False invalid
False invalid
False invalid
False invalid
False invalid
False invalid
Require a date#
A required date should declare this field. Without it, a ModelSerializer falls back to a field
that passes an empty string through to the database, which refuses it with a database error. With
it, the client gets a validation error and nothing is written.
>>> class EventSerializer(serializers.ModelSerializer):
... occurred = PartialDateField()
...
... class Meta:
... model = Event
... fields = ["occurred"]
>>> serializer = EventSerializer(data={"occurred": ""})
>>> serializer.is_valid()
False
>>> serializer.errors["occurred"][0].code
'invalid'
>>> Event.objects.count()
0
To use the field on every serializer in your project, name it in your own
serializer_field_mapping, and derive your serializers from that class.
>>> import partial_dates
>>> class ProjectModelSerializer(serializers.ModelSerializer):
... serializer_field_mapping = {
... **serializers.ModelSerializer.serializer_field_mapping,
... partial_dates.PartialDateField: PartialDateField,
... }
>>> class MappedEventSerializer(ProjectModelSerializer):
... class Meta:
... model = Event
... fields = ["occurred"]
>>> serializer = MappedEventSerializer(data={"occurred": ""})
>>> serializer.is_valid()
False
>>> serializer.errors["occurred"][0].code
'invalid'
Leave a ModelSerializer alone#
A ModelSerializer that declares nothing needs no change for a date that may be empty. Django
REST framework’s fallback reads and writes the same ISO form as this field.
>>> class FallbackSerializer(serializers.ModelSerializer):
... class Meta:
... model = Sample
... fields = ["collected"]
>>> serializer = FallbackSerializer(data={"collected": "1990-06"})
>>> serializer.is_valid()
True
>>> FallbackSerializer(serializer.save()).data["collected"]
'1990-06'
Why an empty string is refused#
The model field stores "" as no date, which suits a form. On an API the wire format is the ISO
form or null, and Django REST framework’s own DateField refuses "" in the same way.
Accepting it would hand a required date the empty string, and the database would refuse it
instead of the serializer. A client that has no date sends null.
To filter a list of records by a partial date or a range of dates, use the filters in the filtering guide, which also covers django-filter’s filter backend.