Menu

ORM Models & Relationships

Relevant source files

Purpose and Scope

This document explains the SQLAlchemy 2.0 async ORM model layer and the entity-relationship structure that defines the system's data model. It covers:

  • Model Class Patterns: Base classes, type annotations with Mapped[], and async compatibility
  • Entity Relationships: Many-to-many junction tables (user_role, role_menu, role_data_scope, data_scope_rule)
  • Model Definitions: Core administrative models (User, Role, Menu, Dept) and plugin models
  • Field Patterns: Primary keys, logical foreign keys, timestamps, and custom column types

For database schema initialization and SQL scripts, see Database Schema & Initialization. For CRUD operations that query and manipulate these models, see CRUD Patterns & Service Layer. For hierarchical data retrieval and tree building, see Tree Structures & Hierarchical Data.


Base Model Classes

The system uses SQLAlchemy 2.0's declarative mapping with Mapped[] type annotations and dataclass-style initialization. All models inherit from base classes that provide common functionality.

Base Class

The Base class defined in backend/common/model.py12-33 serves as the declarative base for all models. It combines MappedAsDataclass and DeclarativeBase from SQLAlchemy 2.0 to provide:

  • Primary Key: id: Mapped[id_key] field with Snowflake ID generation backend/common/model.py20
  • Timestamps: created_time and updated_time with timezone awareness backend/common/model.py21-26
  • Dataclass Initialization: Enables Model(**kwargs) instantiation pattern
  • Async Compatibility: Works with SQLAlchemy 2.0 async sessions

Models inheriting from Base:

DataClassBase Class

The DataClassBase class backend/common/model.py36-40 extends Base for models requiring full dataclass functionality with explicit field initialization during construction. Used when all fields (including id) need to be specified at instantiation.

Models using DataClassBase:

Diagram: Model Class Hierarchy

Sources: backend/common/model.py:12-40, backend/app/admin/model/user.py:12, backend/app/admin/model/role.py:8, backend/app/admin/model/menu.py:8, backend/app/admin/model/dept.py:8, backend/app/admin/model/data_scope.py:8, backend/app/admin/model/data_rule.py:8, backend/plugin/oauth2/model/user_social.py:8, backend/plugin/code_generator/model/business.py:8, backend/plugin/code_generator/model/column.py:8


Core Admin Models

User Model

The User model backend/app/admin/model/user.py12-37 maps to the sys_user table. It declares __tablename__ = 'sys_user' and uses SQLAlchemy 2.0 Mapped[] type annotations for all fields.

Key Fields:

FieldTypeConstraintsDescription
idid_keyPrimary keySnowflake ID
uuidString(64)UniqueUUID4 string for external references
usernameString(64)Unique, IndexedLogin username
nicknameString(64)RequiredDisplay name
passwordString(256)OptionalHashed password with bcrypt
saltLargeBinary(255)OptionalPassword salt
emailString(256)Unique, Indexed, OptionalEmail address
phoneString(11)OptionalPhone number
avatarString(256)OptionalAvatar URL
statusintDefault 1, Indexed0=disabled, 1=active
is_superuserboolDefault FalseSuperuser privileges
is_staffboolDefault FalseBackend admin access
is_multi_loginboolDefault FalseAllow concurrent sessions
join_timedatetimeAuto-generatedRegistration timestamp
last_login_timedatetimeOptional, Auto-updatedLast login timestamp
dept_idBigIntegerOptionalLogical FK to department

Model Implementation Details:

Role Model

The Role model backend/app/admin/model/role.py8-18 maps to the sys_role table with __tablename__ = 'sys_role':

FieldTypeConstraintsDescription
idid_keyPrimary keySnowflake ID
nameString(32)UniqueRole name
statusintDefault 10=disabled, 1=active
is_filter_scopesboolDefault TrueEnable data scope filtering
remarkUniversalTextOptionalRole description

Data Scope Filtering: The is_filter_scopes: Mapped[bool] field backend/app/admin/model/role.py16 controls whether data permission filtering is applied. When False, users with this role bypass data scope restrictions (typically for admin roles).

The Menu model backend/app/admin/model/menu.py8-30 maps to the sys_menu table with __tablename__ = 'sys_menu'. It implements a self-referential tree structure for hierarchical navigation and permissions:

FieldTypeConstraintsDescription
idid_keyPrimary keySnowflake ID
titleString(64)RequiredMenu display title
nameString(64)RequiredMenu name (for routing)
pathString(200)OptionalRoute path
sortintDefault 0Display order
iconString(128)OptionalIcon identifier
typeintDefault 00=directory, 1=menu, 2=button, 3=embedded, 4=external
componentString(256)OptionalFrontend component path
permsString(128)OptionalPermission identifier
statusintDefault 10=disabled, 1=active
displayintDefault 10=hidden, 1=visible
cacheintDefault 10=no cache, 1=cache
linkUniversalTextOptionalExternal link URL
remarkUniversalTextOptionalMenu description
parent_idBigIntegerOptional, IndexedParent menu ID for tree structure

Tree Structure: The parent_id: Mapped[int | None] field backend/app/admin/model/menu.py29 creates a self-referential relationship. Root menus have parent_id = None, child menus reference their parent's id. This enables unlimited nesting depth for navigation hierarchies. See Tree Structures & Hierarchical Data for tree building utilities.

Dept Model

The Dept model backend/app/admin/model/dept.py8-24 maps to the sys_dept table with __tablename__ = 'sys_dept'. Like Menu, it uses a self-referential tree for organizational hierarchies:

FieldTypeConstraintsDescription
idid_keyPrimary keySnowflake ID
nameString(64)RequiredDepartment name
sortintDefault 0Display order
leaderString(32)OptionalDepartment leader name
phoneString(11)OptionalContact phone
emailString(64)OptionalContact email
statusintDefault 10=disabled, 1=active
del_flagboolDefault False0=deleted, 1=exists (soft delete)
parent_idBigIntegerOptional, IndexedParent department ID

Soft Delete: The del_flag: Mapped[bool] field backend/app/admin/model/dept.py20 implements soft deletion with default=False. When True, the department is logically deleted but database records remain. This preserves referential integrity for historical user assignments.

Sources: backend/app/admin/model/user.py:12-37, backend/app/admin/model/role.py:8-18, backend/app/admin/model/menu.py:8-30, backend/app/admin/model/dept.py:8-24, backend/app/admin/model/init.py:10-19


SQLAlchemy 2.0 Field Patterns

The system uses SQLAlchemy 2.0's Mapped[] generic type for all field declarations, providing type safety and IDE support. Several custom type aliases and patterns ensure consistency across models.

id_key Type

The id_key type alias backend/common/model.py9 defines the primary key pattern. All models declare:

  • Type: Resolves to Annotated[int, mapped_column(BigInteger, primary_key=True, autoincrement=True)]
  • init=False: Excludes from dataclass __init__, as IDs are auto-generated by Snowflake ID or database sequence
  • Snowflake ID: Provides globally unique, time-ordered 64-bit integers (see Database Schema & Initialization)

TimeZone Column Type

The TimeZone type backend/database/db_postgres.py15-17 provides timezone-aware datetime handling with automatic conversion. Declared as:

  • Base class fields: created_time and updated_time inherited by all models backend/common/model.py21-26
  • onupdate callback: Automatically updates updated_time on record modification backend/common/model.py26
  • Timezone handling: Converts to UTC for storage, to local timezone on retrieval

UniversalText Type

The UniversalText type alias backend/database/db_postgres.py20-22 provides database-agnostic text storage:

Maps to Text column type across PostgreSQL, MySQL, and SQLite, supporting arbitrary-length strings. Used for:

Logical Foreign Keys

The system uses logical foreign keys instead of database-level FOREIGN KEY constraints. These are BigInteger fields with foreign key semantics but no enforced referential integrity:

Examples:

Design Rationale:

  • Soft Delete Support: Allows logical deletion (del_flag) without cascade constraint violations
  • Plugin Independence: Cross-plugin relationships work without tight database coupling
  • Migration Flexibility: Easier data migration and testing without constraint management
  • Dynamic Relationships: Supports optional relationships and partial data states

CRUD Layer Joins: Relationships are explicitly defined using JoinConfig in CRUD methods backend/app/admin/crud/crud_user.py101:

See CRUD Patterns & Service Layer for join operation details.

Diagram: SQLAlchemy 2.0 Field Type Patterns

Sources: backend/common/model.py:9-26, backend/database/db_postgres.py:15-22, backend/app/admin/model/user.py:17-36, backend/app/admin/model/role.py:17, backend/app/admin/model/menu.py:25-29, backend/plugin/oauth2/model/user_social.py:18, backend/plugin/code_generator/model/column.py:25, backend/app/admin/crud/crud_user.py:101


Many-to-Many Relationships

The system implements many-to-many relationships through junction tables defined as SQLAlchemy Table objects. These are defined in backend/app/admin/model/__init__.py10-19 and imported wherever join operations are needed.

Junction Table Definitions

Four junction tables define the relationship graph:

Junction TableDatabase TableLeft EntityRight EntityPurpose
user_rolesys_user_roleUserRoleUser role assignments
role_menusys_role_menuRoleMenuRole permission grants
role_data_scopesys_role_data_scopeRoleDataScopeRole data scope bindings
data_scope_rulesys_data_scope_ruleDataScopeDataRuleData scope filtering rules

Code Definition: All four tables are defined in backend/app/admin/model/__init__.py10-19 and exported via __all__ for import in CRUD and service layers.

Example Import in CRUD layer backend/app/admin/crud/crud_user.py9-20:

Usage in CRUD Join Operations

The junction tables are used in JoinConfig definitions to construct SQLAlchemy joins. The CRUDUser class backend/app/admin/crud/crud_user.py76-353 demonstrates progressive join complexity:

Basic User List with Roles backend/app/admin/crud/crud_user.py97-105:

Full User Detail with All Relationships backend/app/admin/crud/crud_user.py328-342:

Join Patterns:

  • Junction tables (user_role, role_menu, etc.) have fill_result=False (implicit) - they don't populate result objects
  • Entity models (User, Role, Dept, etc.) have fill_result=True - they populate nested objects in results
  • Joins follow the relationship chain: User → user_role → Role → role_menu → Menu

Relationship Serialization

The select_join_serialize utility backend/utils/serializers.py transforms flat join results into nested object structures. Defined in CRUD layer backend/app/admin/crud/crud_user.py344-353 and called in service layer backend/app/admin/service/user_service.py79

Serialization Format:

Relationship Syntax:

  • 'EntityA-m2o-EntityB': Many-to-one relationship, creates entityA.entityb (singular)
  • 'EntityA-m2m-EntityB': Many-to-many relationship, creates entityA.entitybs (plural, auto-pluralized)
  • 'EntityA-m2m-EntityB:custom_name': Many-to-many with explicit attribute name entityA.custom_name

Service Layer Usage backend/app/admin/service/user_service.py79:

Junction Table Manipulation

CRUD operations manipulate junction tables using direct SQL statements:

Adding User with Roles backend/app/admin/crud/crud_user.py108-131:

Updating User Roles backend/app/admin/crud/crud_user.py154-178:

Deleting User backend/app/admin/crud/crud_user.py291-304:

Entity Relationship Graph

The junction tables create a relationship graph enabling:

  • Permission Checking: Query accessible menus for a role via role_menu
  • Data Filtering: Apply data scopes to queries via role_data_scope and data_scope_rule
  • RBAC Authorization: Verify user permissions by traversing User → Role → Menu chain

Diagram: Many-to-Many Relationship Implementation

Sources: backend/app/admin/model/init.py:10-19, backend/app/admin/crud/crud_user.py:9-20, backend/app/admin/crud/crud_user.py:76-106, backend/app/admin/crud/crud_user.py:108-131, backend/app/admin/crud/crud_user.py:154-178, backend/app/admin/crud/crud_user.py:291-304, backend/app/admin/crud/crud_user.py:306-353, backend/utils/serializers.py, backend/app/admin/service/user_service.py:79


Hierarchical Self-Referential Models

Two models implement tree structures using self-referential parent_id logical foreign keys.

The Menu model backend/app/admin/model/menu.py29 implements a navigation hierarchy:

Tree Properties:

  • Root nodes: parent_id = None
  • Child nodes: parent_id references parent Menu.id
  • Unlimited nesting depth supported
  • Sibling ordering: sort: Mapped[int] field backend/app/admin/model/menu.py17

Menu Type Hierarchy:

  • type=0: Directory (container, no route)
  • type=1: Menu (routable page)
  • type=2: Button (permission-only, no display)
  • type=3: Embedded iframe
  • type=4: External link

Tree Building: See Tree Structures & Hierarchical Data for build_tree and get_vben5_tree_data utilities backend/utils/build_tree.py

Dept Tree Structure

The Dept model backend/app/admin/model/dept.py23 implements organizational hierarchy:

Tree Properties:

Use Cases:

Diagram: Self-Referential Tree Models

Sources: backend/app/admin/model/menu.py:8-30, backend/app/admin/model/dept.py:8-24, backend/utils/build_tree.py:25-132


Plugin Models

Plugin models extend the core system with additional functionality while maintaining architectural consistency.

OAuth2 Plugin - UserSocial Model

The UserSocial model backend/plugin/oauth2/model/user_social.py8-19 stores OAuth2 social login associations in the sys_user_social table:

FieldTypeDescription
idid_keyPrimary key
sidString(256)Third-party user ID from OAuth provider
sourceString(32)OAuth provider name (github, google, linuxdo)
user_idBigIntegerLogical FK to sys_user.id

Purpose: Links local user accounts to external OAuth2 identities, enabling social login while maintaining a unified user system.

Relationship: One local user can have multiple social account bindings (one per OAuth provider).

Code Generator Plugin Models

The code generator plugin uses two models to store table metadata for code generation.

GenBusiness Model

The GenBusiness model backend/plugin/code_generator/model/business.py8-30 stores business table metadata in the gen_business table:

FieldTypeDescription
idid_keyPrimary key
app_nameString(64)Target application module name
table_nameString(256)Database table name (unique)
doc_commentString(256)Documentation comment for generated code
table_commentString(256)Table description from DB schema
class_nameString(64)Generated model class name
schema_nameString(64)Generated Pydantic schema prefix
filenameString(64)Base filename for generated files
default_datetime_columnboolWhether table has created_time/updated_time
api_versionString(32)API version prefix (default: v1)
gen_pathString(256)Output directory path
remarkUniversalTextAdditional notes

GenColumn Model

The GenColumn model backend/plugin/code_generator/model/column.py8-26 stores column metadata in the gen_column table:

FieldTypeDescription
idid_keyPrimary key
nameString(64)Column name
commentString(256)Column description
typeString(32)SQLAlchemy column type (e.g., String, Integer)
pd_typeString(32)Corresponding Pydantic type (e.g., str, int)
defaultUniversalTextDefault value
sortintColumn order in table
lengthintColumn length (for strings)
is_pkboolWhether column is primary key
is_nullableboolWhether column allows NULL
gen_business_idBigIntegerLogical FK to gen_business.id

Relationship: GenBusiness (1) → (many) GenColumn, representing a table's columns.

Diagram: Plugin Model Relationships

Sources: backend/plugin/oauth2/model/user_social.py:8-19, backend/plugin/code_generator/model/business.py:8-30, backend/plugin/code_generator/model/column.py:8-26, backend/common/model.py:12-40, backend/app/admin/model/user.py:12


Naming Conventions and Table Structure

The system follows consistent naming conventions for maintainability and clarity.

Table Naming Patterns

PrefixPurposeExamples
sys_Core system administrative tablessys_user, sys_role, sys_menu, sys_dept
gen_Code generator plugin tablesgen_business, gen_column
(no prefix)Plugin-specific or business tablesCustom application tables

Column Naming Conventions

  • Primary Keys: Always named id with id_key type
  • Foreign Keys: Named as {entity}_id (e.g., dept_id, user_id, parent_id)
  • Timestamps: created_time, updated_time, join_time, last_login_time
  • Status Fields: status (integer), typically 0=disabled, 1=active
  • Boolean Flags: Prefixed with is_ or suffixed with _flag (e.g., is_superuser, del_flag)
  • Text Fields: Use UniversalText for potentially long content (e.g., remark, link)

SQLAlchemy 2.0 Declarative Patterns

All models use modern SQLAlchemy 2.0 syntax:

Type Annotations:

Nullable Fields:

Required Fields:

Non-Init Fields (auto-generated):

Factory Functions:

Index Strategy

Strategic indexing is applied to:

  • Unique Constraints: User.username, User.email, User.uuid, Role.name, GenBusiness.table_name
  • Lookup Fields: User.username (indexed), User.email (indexed), User.status (indexed)
  • Tree Navigation: Menu.parent_id (indexed), Dept.parent_id (indexed)

This indexing strategy optimizes common query patterns like user authentication, role lookups, and tree traversal.

Sources: backend/app/admin/model/user.py, backend/app/admin/model/role.py, backend/app/admin/model/menu.py, backend/app/admin/model/dept.py, backend/plugin/oauth2/model/user_social.py, backend/plugin/code_generator/model/business.py, backend/plugin/code_generator/model/column.py