Models¶
Models in django-api-orm are the primary way to interact with REST API resources. They provide a Django ORM-like interface for working with external APIs.
Defining Models¶
Basic Model Definition¶
A model requires a Pydantic schema class and an API endpoint:
from pydantic import BaseModel
from django_api_orm import APIModel
class UserSchema(BaseModel):
id: int | None = None
name: str
email: str
active: bool = True
class User(APIModel):
_schema_class = UserSchema
_endpoint = "/api/v1/users/"
Required Attributes¶
Every model must define:
_schema_class: A Pydantic BaseModel subclass that defines the data structure_endpoint: The API endpoint path (relative to the base URL)
Optional Attributes¶
You can also define:
_id_field: The name of the ID field (default:"id")Custom methods and properties
Model Registration¶
Before using a model, you must register it with a client:
from django_api_orm import ServiceClient, register_models
with ServiceClient(base_url="https://api.example.com") as client:
register_models(client, User)
# Now User.objects is available
Multiple Model Registration¶
You can register multiple models at once:
register_models(client, User, Post, Comment)
Model Instances¶
Creating Instances¶
There are several ways to create model instances:
# Using Manager.create()
user = User.objects.create(name="Alice", email="alice@example.com")
# Using the constructor and save()
user = User(name="Alice", email="alice@example.com", _client=client)
user.save()
# Using Manager.get_or_create()
user, created = User.objects.get_or_create(
email="alice@example.com",
defaults={"name": "Alice"}
)
Accessing Attributes¶
Model instances behave like Pydantic models:
user = User.objects.get(id=1)
# Access attributes
print(user.name)
print(user.email)
# Set attributes
user.name = "Alice Updated"
user.email = "alice.updated@example.com"
Instance Methods¶
save()¶
Save the instance to the API:
user = User.objects.get(id=1)
user.name = "New Name"
user.save()
Partial updates are supported:
user.email = "newemail@example.com"
user.save(update_fields=["email"])
delete()¶
Delete the instance from the API:
user = User.objects.get(id=1)
user.delete()
refresh_from_api()¶
Refresh the instance with the latest data from the API:
user = User.objects.get(id=1)
# ... some time passes ...
user.refresh_from_api() # Get latest data
to_dict()¶
Convert the instance to a dictionary:
user = User.objects.get(id=1)
data = user.to_dict()
# {"id": 1, "name": "Alice", "email": "alice@example.com", "active": True}
# Exclude unset fields
data = user.to_dict(exclude_unset=True)
Class Methods¶
from_api()¶
Create an instance from API response data:
data = {"id": 1, "name": "Alice", "email": "alice@example.com"}
user = User.from_api(data, client=client)
get_schema_class()¶
Get the Pydantic schema class:
schema_class = User.get_schema_class()
# Returns UserSchema
Model Validation¶
All data is validated using Pydantic before being sent to the API:
from pydantic import EmailStr, Field
class UserSchema(BaseModel):
id: int | None = None
name: str = Field(..., min_length=1, max_length=100)
email: EmailStr
age: int = Field(..., ge=0, le=150)
class User(APIModel):
_schema_class = UserSchema
_endpoint = "/api/v1/users/"
# This will raise a validation error
try:
user = User.objects.create(name="", email="invalid", age=200)
except ValidationError as e:
print(e)
Async Models¶
For async operations, use AsyncAPIModel:
from django_api_orm import AsyncAPIModel, AsyncServiceClient, register_async_models
class User(AsyncAPIModel):
_schema_class = UserSchema
_endpoint = "/api/v1/users/"
async with AsyncServiceClient(base_url="https://api.example.com") as client:
register_async_models(client, User)
# Await async operations
user = await User.objects.create(name="Alice", email="alice@example.com")
await user.save()
await user.delete()
See Async Support for more details on async usage.
Custom Model Behavior¶
You can add custom methods and properties to your models:
class User(APIModel):
_schema_class = UserSchema
_endpoint = "/api/v1/users/"
def is_admin(self) -> bool:
"""Check if user is an admin."""
return self.role == "admin"
@property
def full_name(self) -> str:
"""Get user's full name."""
return f"{self.first_name} {self.last_name}"
def deactivate(self) -> None:
"""Deactivate this user."""
self.active = False
self.save(update_fields=["active"])
# Usage
user = User.objects.get(id=1)
if user.is_admin():
print(f"Admin: {user.full_name}")
user.deactivate()
Best Practices¶
Use descriptive schema names:
UserSchema,PostSchema, etc.Define optional fields correctly: Use
field: type | None = Nonefor optional fieldsUse update_fields for partial updates: More efficient and prevents overwriting unchanged data
Handle validation errors: Always wrap create/save operations in try/except blocks
Refresh when needed: Use
refresh_from_api()when data might be staleCustom methods for business logic: Keep API interaction logic in the model
Next Steps¶
Learn about QuerySets for querying data
Explore Managers for creating and managing instances
See Exceptions for error handling