Field Reference¶
How each Django field is converted into a Pydantic annotation.
Scalar fields¶
| Django field | Python type | Notes |
|---|---|---|
CharField |
str |
max_length becomes a MaxLen constraint |
TextField |
str |
|
SlugField |
str |
|
FileField, FilePathField |
str |
A FieldFile serializes to its .url |
EmailField |
EmailStr |
Requires pydantic[email] |
URLField |
AnyUrl |
|
UUIDField |
UUID |
|
AutoField, IntegerField, SmallIntegerField, BigIntegerField, PositiveIntegerField, PositiveSmallIntegerField |
int |
|
FloatField |
float |
|
DecimalField |
Decimal |
|
BooleanField, NullBooleanField |
bool |
|
DateTimeField |
datetime.datetime |
|
DateField |
datetime.date |
|
TimeField |
datetime.time |
|
DurationField |
datetime.timedelta |
|
BinaryField |
bytes |
|
IPAddressField, GenericIPAddressField |
IPvAnyAddress |
|
JSONField |
Json |
Django 3.2+ |
Any field with choices |
Generated Enum |
See Django Choices |
PostgreSQL fields¶
| Django field | Python type |
|---|---|
ArrayField |
list[<base field type>] |
HStoreField |
Json |
JSONField (django.contrib.postgres) |
Json |
RangeField |
list[<base field type>] |
Verified against a real model¶
class SpeakerProfile(models.Model):
"""Exercises every scalar field conversion documented in the field reference."""
uuid = models.UUIDField()
full_name = models.CharField(max_length=120)
biography = models.TextField(blank=True)
slug = models.SlugField()
email = models.EmailField()
website = models.URLField()
talks_given = models.IntegerField(default=0)
rating = models.FloatField(null=True)
fee = models.DecimalField(max_digits=8, decimal_places=2, null=True)
is_active = models.BooleanField(default=True)
joined_at = models.DateTimeField(null=True)
birth_date = models.DateField(null=True)
preferred_slot = models.TimeField(null=True)
session_length = models.DurationField(null=True)
last_login_ip = models.GenericIPAddressField(null=True)
metadata = models.JSONField(null=True)
class Meta:
app_label = 'examples'
type_name below only shortens the display: Python 3.14 renders Optional[int]
as int | None, so printing the annotation raw would read differently depending on
the interpreter. The annotation objects themselves are the same on every version.
>>> from typing import get_args
>>> def type_name(annotation):
... args = get_args(annotation)
... if type(None) in args:
... inner = ', '.join(type_name(arg) for arg in args if arg is not type(None))
... return f'Optional[{inner}]'
... return getattr(annotation, '__name__', str(annotation))
>>> class SpeakerProfileSchema(ModelSchema):
... class Config:
... model = models.SpeakerProfile
>>> for name, info in SpeakerProfileSchema.model_fields.items():
... print(f'{name:16} {type_name(info.annotation)}')
id Optional[int]
uuid UUID
full_name str
biography Optional[str]
slug str
email EmailStr
website AnyUrl
talks_given int
rating Optional[float]
fee Optional[Decimal]
is_active bool
joined_at Optional[datetime]
birth_date Optional[date]
preferred_slot Optional[time]
session_length Optional[timedelta]
last_login_ip Optional[IPvAnyAddress]
metadata Optional[Json]
Relation fields¶
| Django field | depth = 0 |
depth ≥ 1 |
|---|---|---|
ForeignKey, OneToOneField |
int (related pk), alias <name>_id |
Nested schema |
ManyToManyField |
list[int] of related pks |
list[NestedSchema] |
OneToOneRel (reverse) |
int |
Nested schema |
ManyToOneRel (reverse FK) |
Not generated | Not generated |
ManyToManyRel (reverse M2M) |
Not generated | Not generated |
Reverse ForeignKey and reverse ManyToMany are skipped when fields are
collected, so a schema never silently acquires a field that triggers a query.
Declare them with Source.
Relations are written through <name>_id
create() and update() detect relation fields and hand the ORM the
<name>_id attribute, which is the form Django accepts for a primary key.
Many-to-many values are applied after the instance is saved, since they need
a pk to attach to.
Relations are read as the related primary key
A relation field accepts either the related instance or its primary key, so reading a Django object and accepting a pk from a JSON payload both work:
What makes a field optional¶
Three independent rules can relax a generated field. Any one of them is enough.
| Rule | Effect |
|---|---|
null=True on the Django field |
Type becomes Optional[...], default None |
blank=True on the Django field |
Type becomes Optional[...], default None |
Listed in Config.optional |
Type becomes Optional[...], default None |
| Is the primary key | Relaxed unless named in fields — see the pk rule |
blank=True is worth calling out: it is a form-level flag in Django, not a
database one, but it does relax the schema field.
>>> SpeakerProfileSchema.model_fields['biography'].is_required() # blank=True
False
>>> SpeakerProfileSchema.model_fields['rating'].is_required() # null=True
False
>>> SpeakerProfileSchema.model_fields['full_name'].is_required() # neither
True
Defaults¶
A Django default becomes the Pydantic default. A callable default becomes a
default_factory.
>>> SpeakerProfileSchema.model_fields['talks_given'].default
0
>>> SpeakerProfileSchema.model_fields['is_active'].default
True
Metadata carried across¶
verbose_name becomes the field title, help_text (falling back to
verbose_name) becomes the description, and max_length becomes a constraint:
>>> info = SpeakerProfileSchema.model_fields['full_name']
>>> info.title
'Full Name'
>>> info.metadata
[MaxLen(max_length=120)]
All three surface in the JSON Schema:
>>> print(json.dumps(SpeakerProfileSchema.model_json_schema()['properties']['full_name'], indent=2))
{
"description": "",
"maxLength": 120,
"title": "Full Name",
"type": "string"
}
max_length is dropped for custom types
Fields converted to a non-str Pydantic type — EmailStr, AnyUrl, and
any choices enum — do not carry max_length, because the constraint does
not apply to the converted type.
Unsupported fields¶
A field with no registered converter raises at class-creation time rather than producing a silently wrong schema:
Handle it by excluding the field, or by declaring the annotation yourself.
Related pages¶
- Django Choices — enum generation in detail
- Relations — relation fields and
depth - ModelSchema configuration — every
Configoption