Skip to content

Django ORM

erdify parses Django models from source — no Django runtime, settings, or app registry required.

A models.Model subclass becomes an entity; abstract bases (class Meta: abstract = True) are inherited but not drawn, and a class Meta: db_table = "..." overrides the table name.

from django.db import models


class Author(models.Model):          # implicit `id` primary key (int)
    name = models.CharField(max_length=100)


class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)   # N:1
    editor = models.ForeignKey(Author, null=True, on_delete=models.SET_NULL,
                               related_name="+")                     # N:0..1
    tags = models.ManyToManyField("Tag")                            # M:N
    profile = models.OneToOneField("Profile", on_delete=models.CASCADE)  # 1:1

    class Meta:
        db_table = "catalog_book"

ForeignKey and OneToOneField are modeled as the <name>_id column they create in the database. A ForeignKey(null=True) is drawn optional on the referenced side (}o--o|). A OneToOneField is a unique foreign key, so it is marked UK and drawn 1:1 like ForeignKey(unique=True): |o--||, or |o--o| with null=True. A unique=True column is marked UK. A ForeignKey(primary_key=True) or OneToOneField(primary_key=True) (shared primary key) makes <name>_id the table's primary key (no implicit id is added) and is drawn 1:1 as well.

The column references the target's primary key, including one inherited from an abstract base, or the to_field= column, and takes that column's type (foreign_table in JSON output). A target outside the parsed files is assumed to reference its implicit id (or the to_field= column), typed as Django's default auto field.

Inheritance

Inheritance follows Django's three model inheritance styles:

Base Result
Abstract base (class Meta: abstract = True) Not drawn; its fields are copied into each subclass.
Concrete model (multi-table inheritance) The subclass gets its own table with only its own fields (plus those of its abstract bases) and a <parent>_ptr_id column: a one-to-one link to the parent, drawn 1:1. The parent's fields stay on the parent.
Proxy model (class Meta: proxy = True) Not drawn: a proxy has no table. A relation to a proxy, or a subclass of one, points at the model the proxy stands for.
class Place(models.Model):
    name = models.CharField(max_length=50)


class Restaurant(Place):             # place_ptr_id PK,FK -> place.id
    serves_pizza = models.BooleanField()

As in Django:

  • The link is named after the parent's lower-cased class name (OrderItem → orderitem_ptr_id).
  • It is the subclass's primary key unless the subclass declares one; with several concrete parents, only the first link is the primary key and the others are unique one-to-one columns. The primary key is listed first.
  • An explicit OneToOneField(Place, parent_link=True), in the subclass or one of its abstract bases, replaces the implicit link.
  • An abstract base that subclasses a concrete model passes its link on to the models built from it.
  • A foreign key to the subclass references its link column; a to_field= naming one of the parent's fields references the parent's table.

Relationship targets

Relationship targets are resolved by class name, including "self" and "app.Model" string references. A ManyToManyField(through=LinkModel) is drawn through the link model's own foreign keys (no spurious direct edge), exactly like SQLAlchemy secondary=.

By default Django field types are mapped to readable Python types (CharField → str, IntegerField/AutoField → int, DateTimeField → datetime, …) so mixed-source diagrams stay consistent. Ambiguous or unknown fields (JSONField, FileField, custom/third-party fields) keep their Django name rather than fake a type. Pass --django-raw-types to show the original Django field names everywhere instead.

models.TextChoices / models.IntegerChoices classes are rendered as enums, and a field that references one via choices=Status.choices (or choices=Status) is linked to that enum. Inline choices=[("a", "A"), …] tuples are anonymous and not rendered as enums.