Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.veloiq.dev/llms.txt

Use this file to discover all available pages before exploring further.

Models are the foundation of every VeloIQ application. Define a model once in Python and the framework automatically generates a REST API, an SQLAdmin back-office view, and TypeScript schemas for the React frontend — no repetition required.

Base classes

VeloIQ provides three base classes. Choose the one that matches your storage requirements; all three are imported directly from veloiq_framework.

FrameworkModel

FrameworkModel is the minimal base. It provides a single auto-increment integer primary key named id and nothing else — no audit columns, no extra metadata. Use it when you want the UI to show only the fields you declare.
from veloiq_framework import FrameworkModel

class Category(FrameworkModel, table=True):
    __tablename__ = "category"
    name: str
    slug: str

TimestampedModel

TimestampedModel extends FrameworkModel with automatic created_at and updated_at columns. The code generator appends these two fields after all fields you declare, so they appear last in every list, form, and detail view.
from veloiq_framework import TimestampedModel

class Order(TimestampedModel, table=True):
    __tablename__ = "order"
    reference: str
    total: float
    status: str = "pending"
Use TimestampedModel as your default. Audit timestamps are almost always useful, and you can always switch to FrameworkModel later if a specific model genuinely doesn’t need them.

StandardModel

StandardModel is for applications that must stay compatible with a CubicWeb database. It uses eid as the Python attribute name, mapped to the cw_eid physical column, and follows cw_ column naming conventions. New greenfield applications should use FrameworkModel or TimestampedModel instead.
from veloiq_framework import StandardModel
from sqlmodel import Field
from sqlalchemy import Column, String

class LegacyItem(StandardModel, table=True):
    __tablename__ = "legacy_item"
    cw_name: str = Field(sa_column=Column("cw_name", String))

Field types

Standard Python and SQLModel types work directly on all base classes. Use Optional[T] for nullable columns and set default=None or any other default value as needed.
from typing import Optional
from veloiq_framework import TimestampedModel

class Product(TimestampedModel, table=True):
    __tablename__ = "product"

    name: str
    price: float
    in_stock: bool = True
    description: Optional[str] = None

Relations with jm_relationship()

Use jm_relationship() to declare SQLModel relationships. The function is a thin wrapper around SQLModel’s Relationship that attaches cardinality metadata — the frontend reads this to render required/optional indicators and pagination hints.

One-to-many

Declare the collection on the parent and the scalar reference on the child, each pointing back at the other with back_populates.
from typing import List, Optional
from sqlmodel import Field
from veloiq_framework import TimestampedModel, jm_relationship

class Customer(TimestampedModel, table=True):
    __tablename__ = "customer"
    name: str
    orders: List["Order"] = jm_relationship(back_populates="customer")

class Order(TimestampedModel, table=True):
    __tablename__ = "order"
    reference: str
    customer_id: Optional[int] = Field(default=None, foreign_key="customer.id")
    customer: Optional["Customer"] = jm_relationship(back_populates="orders")

Self-referential

A model can reference itself to represent hierarchies such as tasks with subtasks. Self-referential relationships automatically trigger Miller column rendering in the frontend — clicking a row drills into its children in an adjacent column.
from typing import List, Optional
from sqlmodel import Field
from veloiq_framework import TimestampedModel, jm_relationship

class Task(TimestampedModel, table=True):
    __tablename__ = "task"
    title: str
    parent_id: Optional[int] = Field(default=None, foreign_key="task.id")
    subtasks: List["Task"] = jm_relationship(
        back_populates="parent",
        sa_relationship_kwargs={"foreign_keys": "[Task.parent_id]"},
    )
    parent: Optional["Task"] = jm_relationship(
        back_populates="subtasks",
        sa_relationship_kwargs={"foreign_keys": "[Task.parent_id]"},
    )

jm_relationship() parameters

ParameterTypeDescription
back_populatesstrAttribute name on the related model that points back to this one
min_itemsintMinimum cardinality (default 0)
max_itemsint | NoneMaximum cardinality; None means unbounded (default None)
requiredboolWhether the relation is required in UI forms (default False)
sa_relationship_kwargsdictExtra keyword arguments forwarded to SQLAlchemy’s relationship()

Field options with veloiq_field()

Use veloiq_field() in place of a plain Field() to add per-role read and write restrictions to individual columns. For example, to allow only Admins to read or change an employee’s salary:
from veloiq_framework import veloiq_field, TimestampedModel

class Employee(TimestampedModel, table=True):
    __tablename__ = "employee"
    name: str
    department: str
    salary: float = veloiq_field(
        default=0.0,
        read_roles=["Admin"],
        write_roles=["Admin"],
    )
veloiq_field() accepts all the same keyword arguments as pydantic.Field. See Access Control for the full details on read_roles, write_roles, and how field-level restrictions interact with the broader RBAC system.

Code generation

After defining or changing your models, run veloiq generate from the backend directory:
veloiq generate
This writes two files for each module:
  • backend/app/modules/{module}/api.py — CRUD REST endpoints (do not edit)
  • frontend/src/pages/{module}/{module}Schema.gen.ts — TypeScript field definitions (do not edit)
Re-run veloiq generate every time you add, rename, or remove a field or model.

Modules

Understand how modules are structured and auto-loaded

Access Control

Apply RBAC and ReBAC to your models and fields