Skip to content

Pydantic2Django Relationships Accessor Details

This document explains how relationships between source models (Pydantic/Dataclass) and Django models are tracked and resolved via RelationshipMapper and RelationshipConversionAccessor.

Core Data Structures

```15:31:src/pydantic2django/core/relationships.py @dataclass class RelationshipMapper: """ Bidirectional mapper between source models (Pydantic/Dataclass) and Django models. """

# Allow storing either source type
pydantic_model: Optional[type[BaseModel]] = None
dataclass_model: Optional[type] = None
django_model: Optional[type[models.Model]] = None
context: Optional[ModelContext] = None  # Keep context if needed later

@property
def source_model(self) -> Optional[type]:
    """Return the source model (either Pydantic or Dataclass)."""
    return self.pydantic_model or self.dataclass_model

37:41:src/pydantic2django/core/relationships.py @dataclass class RelationshipConversionAccessor: available_relationships: list[RelationshipMapper] = field(default_factory=list) ```

Importing and Exporting Mappings

```42:73:src/pydantic2django/core/relationships.py @classmethod def from_dict(cls, relationship_mapping_dict: dict) -> "RelationshipConversionAccessor": """ Convert a dictionary of strings representing model qualified names to a RelationshipConversionAccessor """ available_relationships = [] for pydantic_mqn, django_mqn in relationship_mapping_dict.items(): ... available_relationships.append(RelationshipMapper(pydantic_model, django_model, context=None)) return cls(available_relationships)

```74:92:src/pydantic2django/core/relationships.py
def to_dict(self) -> dict:
    """
    Convert the relationships to a dictionary of strings representing
    model qualified names for bidirectional conversion.
    """
    relationship_mapping_dict = {}
    for relationship in self.available_relationships:
        ...
        relationship_mapping_dict[pydantic_mqn] = django_mqn
    return relationship_mapping_dict

Name formats for stable serialization:

```93:104:src/pydantic2django/core/relationships.py def _get_pydantic_model_qualified_name(self, model: type[BaseModel] | None) -> str: if model is None: return "" return f"{model.module}.{model.name}"

def _get_django_model_qualified_name(self, model: type[models.Model] | None) -> str: if model is None: return "" return f"{model._meta.app_label}.{model.name}"

## Discovering and Adding Models

List and query known models:

```105:120:src/pydantic2django/core/relationships.py
@property
def available_source_models(self) -> list[type]:
    ...

@property
def available_django_models(self) -> list[type[models.Model]]:
    return [r.django_model for r in self.available_relationships if r.django_model is not None]

Add models incrementally:

```121:129:src/pydantic2django/core/relationships.py def add_pydantic_model(self, model: type[BaseModel]) -> None: ... self.available_relationships.append(RelationshipMapper(model, None, context=None))

```130:140:src/pydantic2django/core/relationships.py
def add_dataclass_model(self, model: type) -> None:
    ...
    self.available_relationships.append(RelationshipMapper(dataclass_model=model))

```141:149:src/pydantic2django/core/relationships.py def add_django_model(self, model: type[models.Model]) -> None: ... self.available_relationships.append(RelationshipMapper(None, None, model, context=None))

## Mapping and Lookup APIs

Lookups in either direction:

```150:170:src/pydantic2django/core/relationships.py
def get_django_model_for_pydantic(self, pydantic_model: type[BaseModel]) -> Optional[type[models.Model]]:
    for relationship in self.available_relationships:
        if relationship.pydantic_model == pydantic_model and relationship.django_model is not None:
            return relationship.django_model
    return None

def get_pydantic_model_for_django(self, django_model: type[models.Model]) -> Optional[type[BaseModel]]:
    for relationship in self.available_relationships:
        if relationship.django_model == django_model and relationship.pydantic_model is not None:
            return relationship.pydantic_model
    return None

Dataclass to Django lookup:

```172:181:src/pydantic2django/core/relationships.py def get_django_model_for_dataclass(self, dataclass_model: type) -> Optional[type[models.Model]]: for relationship in self.available_relationships: if relationship.dataclass_model == dataclass_model and relationship.django_model is not None: return relationship.django_model return None

Create or update mappings in a single call:

```183:241:src/pydantic2django/core/relationships.py
def map_relationship(self, source_model: type, django_model: type[models.Model]) -> None:
    source_type = (
        "pydantic" if isinstance(source_model, type) and issubclass(source_model, BaseModel) else
        "dataclass" if dataclasses.is_dataclass(source_model) else
        "unknown"
    )
    if source_type == "unknown":
        logger.warning(...)
        return
    # Update existing or append a new RelationshipMapper
    ...

Known source-model checks and name-based lookup:

```242:253:src/pydantic2django/core/relationships.py def is_source_model_known(self, model: type) -> bool: is_pydantic = isinstance(model, type) and issubclass(model, BaseModel) is_dataclass = dataclasses.is_dataclass(model) ...

```254:263:src/pydantic2django/core/relationships.py
def get_source_model_by_name(self, model_name: str) -> Optional[type]:
    for r in self.available_relationships:
        if r.pydantic_model and r.pydantic_model.__name__ == model_name:
            return r.pydantic_model
        if r.dataclass_model and r.dataclass_model.__name__ == model_name:
            return r.dataclass_model
    return None

Typical Usage Patterns

  • Initialize empty, add and map on the fly:
from pydantic import BaseModel
from django.db import models
from pydantic2django.core.relationships import RelationshipConversionAccessor

class PostModel(BaseModel):
    ...

class BlogPost(models.Model):
    ...

rel = RelationshipConversionAccessor()
rel.add_pydantic_model(PostModel)
rel.add_django_model(BlogPost)
rel.map_relationship(PostModel, BlogPost)

# Lookups
assert rel.get_django_model_for_pydantic(PostModel) is BlogPost
  • Serialize/restore mapping (e.g., persisted in JSONField):
mapping_dict = rel.to_dict()
rel2 = RelationshipConversionAccessor.from_dict(mapping_dict)

Integration with Type Mapping

The RelationshipConversionAccessor is used by the type mapping system to: - Check if a source model is known before selecting relationship units (FK/M2M/O2O) - Resolve to targets (including self-references and app labels)

See the relationship checks and resolution within the mapper where it calls into the accessor to determine known models and resolve target Django models.