Skip to content

ORM Reference

Complete reference for Aksara's ORM — all field types, query methods, and model options.


Field Types

All fields are imported from aksara.fields:

from aksara import fields

String Fields

Field Description Common Options
fields.String Text with maximum length max_length, default, null
fields.Text Unlimited text default, null
fields.Email Email addresses (validated) unique, default, null
fields.URL Web URLs (validated) default, null
fields.Slug URL-safe strings max_length, unique
fields.UUID Unique identifiers default, primary_key
# Example usage
class Article(Model):
    title = fields.String(max_length=200)      # Required, up to 200 chars
    body = fields.Text()                        # Unlimited length
    author_email = fields.Email(unique=True)   # Must be valid email
    source_url = fields.URL(null=True)         # Optional URL
    slug = fields.Slug(max_length=100)         # URL-safe: "my-article"

Numeric Fields

Field Description Common Options
fields.Integer Whole numbers default, null
fields.SmallInteger Small whole numbers (-32768..32767) default, null
fields.BigInteger Large whole numbers default, null
fields.PositiveInteger Non-negative integers default, null
fields.PositiveSmallInteger Non-negative small integers (0..32767) default, null
fields.PositiveBigInteger Non-negative large integers default, null
fields.Float Decimal numbers default, null
fields.Decimal Precise decimals (money) max_digits, decimal_places
fields.Boolean True/False default, null
class Product(Model):
    quantity = fields.Integer(default=0)        # Stock count
    price = fields.Decimal(                     # Precise money
        max_digits=10,                          # Up to 10 total digits
        decimal_places=2                        # 2 after decimal point
    )
    rating = fields.Float(null=True)            # Average rating
    is_available = fields.Boolean(default=True) # In stock?

Date and Time Fields

Field Description Common Options
fields.DateTime Date and time auto_now, auto_now_add, default
fields.Date Date only auto_now, auto_now_add
fields.Time Time only default
fields.Duration Time spans default
class Event(Model):
    # Automatically set when created
    created_at = fields.DateTime(auto_now_add=True)

    # Automatically updated on every save
    updated_at = fields.DateTime(auto_now=True)

    # User-provided values
    event_date = fields.Date()
    start_time = fields.Time()
    duration = fields.Duration()  # e.g., timedelta(hours=2)

Relationship Fields

Field Description Required Options
fields.ForeignKey Many-to-one relationship to, on_delete
fields.OneToOne One-to-one relationship to, on_delete
fields.ManyToMany Many-to-many relationship to
class Comment(Model):
    # Many comments belong to one article
    article = fields.ForeignKey(
        to="Article",                           # Related model
        on_delete=fields.CASCADE,               # Delete comment if article deleted
        related_name="comments"                 # Access from article: article.comments
    )

class Profile(Model):
    # One profile per user
    user = fields.OneToOne(
        to="User",
        on_delete=fields.CASCADE,
        related_name="profile"                  # Access: user.profile
    )

class Article(Model):
    # Articles can have many tags, tags can be on many articles
    tags = fields.ManyToMany(
        to="Tag",
        related_name="articles"                 # Access: tag.articles
    )

Special Fields

Field Description Common Options
fields.JSON JSON data default, null
fields.Array List of values base_field
fields.File File uploads upload_to
fields.Image Image uploads upload_to
fields.IP IP addresses protocol, null
fields.Binary Raw binary data (BYTEA) null
fields.FilePath Filesystem paths path, match, recursive
class Configuration(Model):
    settings = fields.JSON(default=dict)        # {"theme": "dark", "lang": "en"}
    tags = fields.Array(base_field=fields.String(max_length=50))  # ["python", "web"]

class Document(Model):
    file = fields.File(upload_to="documents/")
    thumbnail = fields.Image(upload_to="thumbnails/")

Field Options

Options that work on all (or most) field types:

Option Type Description
null bool Allow NULL in database (default: False)
blank bool Allow empty in forms (default: False)
default any Default value when not provided
unique bool Must be unique across all rows
primary_key bool Use as primary key instead of auto id
db_index bool Create database index for faster lookups
db_column str Custom column name in database
choices list Limit to specific values
validators list Custom validation functions
verbose_name str Human-readable name
help_text str Description for forms/docs
class User(Model):
    email = fields.Email(
        unique=True,                            # No duplicates
        db_index=True,                          # Fast lookups by email
        verbose_name="Email Address",
        help_text="User's primary email"
    )

    status = fields.String(
        max_length=20,
        choices=[                               # Only these values allowed
            ("active", "Active"),
            ("inactive", "Inactive"),
            ("pending", "Pending"),
        ],
        default="pending"
    )

on_delete Options

When a ForeignKey target is deleted:

Option What Happens
fields.CASCADE Delete this object too
fields.PROTECT Prevent deletion (raise error)
fields.SET_NULL Set to NULL (requires null=True)
fields.SET_DEFAULT Set to default value
fields.DO_NOTHING Do nothing (may break integrity)
class Comment(Model):
    # If article deleted, delete all its comments
    article = fields.ForeignKey(Article, on_delete=fields.CASCADE)

    # Can't delete author if they have comments
    author = fields.ForeignKey(User, on_delete=fields.PROTECT)

    # If parent comment deleted, set to NULL (orphan)
    parent = fields.ForeignKey(
        "self",
        on_delete=fields.SET_NULL,
        null=True
    )

QuerySet Methods

Retrieving Objects

Method Returns Description
.all() QuerySet All objects
.get(**kwargs) Object Single object matching criteria
.first() Object/None First object or None
.last() Object/None Last object or None
.filter(**kwargs) QuerySet Objects matching criteria
.exclude(**kwargs) QuerySet Objects NOT matching criteria
# Get all users
users = await User.objects.all()

# Get one specific user (raises DoesNotExist if not found)
user = await User.objects.get(id=1)
user = await User.objects.get(email="test@example.com")

# Get first/last
first_user = await User.objects.first()
latest = await User.objects.order_by("-created_at").first()

# Filter (returns QuerySet, can have multiple results)
active_users = await User.objects.filter(is_active=True)

Filter Lookups

Use double underscores for comparisons:

Lookup SQL Equivalent Example
field = name="John"
field__exact = name__exact="John"
field__iexact ILIKE name__iexact="john" (case-insensitive)
field__contains LIKE '%x%' name__contains="oh"
field__icontains ILIKE '%x%' name__icontains="oh"
field__startswith LIKE 'x%' name__startswith="J"
field__endswith LIKE '%x' name__endswith="n"
field__gt > age__gt=18
field__gte >= age__gte=18
field__lt < age__lt=65
field__lte <= age__lte=65
field__in IN (...) status__in=["active", "pending"]
field__isnull IS NULL deleted_at__isnull=True
field__range BETWEEN age__range=(18, 65)
# Users named John (case-insensitive)
await User.objects.filter(name__iexact="john")

# Users with gmail addresses
await User.objects.filter(email__endswith="@gmail.com")

# Users older than 18
await User.objects.filter(age__gt=18)

# Users in multiple statuses
await User.objects.filter(status__in=["active", "pending"])

# Users created in date range
from datetime import date
await User.objects.filter(
    created_at__range=(date(2024, 1, 1), date(2024, 12, 31))
)

Ordering

Method Description
.order_by("field") Ascending order
.order_by("-field") Descending order (note the -)
.order_by("f1", "f2") Multiple fields
# Oldest first
users = await User.objects.order_by("created_at")

# Newest first (note the minus sign)
users = await User.objects.order_by("-created_at")

# Sort by status, then by name within each status
users = await User.objects.order_by("status", "name")

Limiting Results

Method Description
[:n] First n results
[n:m] Results from n to m
.limit(n) First n results
.offset(n) Skip first n results
# First 10 users
first_ten = await User.objects.all()[:10]

# Skip first 10, get next 10 (pagination)
page_two = await User.objects.all()[10:20]

# Using methods
await User.objects.limit(10).offset(20)  # Page 3

Aggregations

Method Returns Description
.count() int Number of objects
.exists() bool True if any objects exist
.aggregate(...) dict Computed values
.values("field") QuerySet Dictionaries instead of objects
.values_list("field") QuerySet Tuples instead of objects
.distinct() QuerySet Remove duplicates
# How many users?
total = await User.objects.count()

# Any active users?
has_active = await User.objects.filter(is_active=True).exists()

# Sum and average
from aksara.db import Sum, Avg
stats = await Order.objects.aggregate(
    total=Sum("amount"),
    average=Avg("amount")
)
# {"total": 50000, "average": 125.50}

# Just the emails (as dictionaries)
emails = await User.objects.values("email")
# [{"email": "a@b.com"}, {"email": "c@d.com"}]

# Just the emails (as tuples)
emails = await User.objects.values_list("email", flat=True)
# ["a@b.com", "c@d.com"]

Modifying Data

Method Description
.create(**kwargs) Create and save new object
.update(**kwargs) Update all matching objects
.delete() Delete all matching objects
.get_or_create(**kwargs) Get existing or create new
.update_or_create(**kwargs) Update existing or create new
.bulk_create(objects) Create many objects at once
.bulk_update(objects, fields) Update many objects at once
# Create
user = await User.objects.create(
    email="new@example.com",
    name="New User"
)

# Update all matching objects
await User.objects.filter(is_active=False).update(status="inactive")

# Delete all matching objects
await User.objects.filter(status="deleted").delete()

# Get or create (won't duplicate)
user, created = await User.objects.get_or_create(
    email="test@example.com",
    defaults={"name": "Test User"}  # Only used if creating
)

# Bulk create (efficient for many objects)
users = [
    User(email="a@b.com", name="A"),
    User(email="c@d.com", name="C"),
]
await User.objects.bulk_create(users)

Model Class Options

Set in the Meta class inside your model:

class Article(Model):
    title = fields.String(max_length=200)

    class Meta:
        table_name = "articles"          # Custom table name
        ordering = ["-created_at"]       # Default ordering
        unique_together = [              # Compound unique constraints
            ("author", "slug")
        ]
        indexes = [                      # Database indexes
            ["status", "created_at"]
        ]
        verbose_name = "Article"
        verbose_name_plural = "Articles"
Option Type Description
table_name str Custom database table name
ordering list Default sort order
unique_together list Compound uniqueness constraints
indexes list Database indexes
verbose_name str Human-readable name
verbose_name_plural str Plural form of name
abstract bool Don't create table (for inheritance)

Model Methods

Method Description
await obj.save() Save to database
await obj.delete() Delete from database
await obj.refresh_from_db() Reload from database
obj.pk Primary key value
# Create and save
user = User(email="test@example.com", name="Test")
await user.save()

# Modify and save
user.name = "Updated Name"
await user.save()

# Delete
await user.delete()

# Reload from database (if modified elsewhere)
await user.refresh_from_db()

Following Relationships

# Forward: Comment → Article
comment = await Comment.objects.get(id=1)
article = await comment.article  # Get the article

# Reverse: Article → Comments
article = await Article.objects.get(id=1)
comments = await article.comments.all()  # Get all comments

Prefetching (Avoiding N+1 Queries)

# BAD: N+1 queries
articles = await Article.objects.all()
for article in articles:
    author = await article.author  # Query for each article!

# GOOD: 2 queries total
articles = await Article.objects.select_related("author").all()
for article in articles:
    author = article.author  # Already loaded!

# For reverse relations (many), use prefetch_related
articles = await Article.objects.prefetch_related("comments").all()

Manager Reference

Every model has a objects manager:

User.objects.all()      # Default manager
User.objects.filter()   # Also on default manager

Custom Managers

class PublishedManager(Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status="published")

class Article(Model):
    status = fields.String(max_length=20)

    objects = Manager()               # Default: all articles
    published = PublishedManager()    # Only published articles

# Usage
all_articles = await Article.objects.all()
published_only = await Article.published.all()

See Also