Get the FREE Ultimate OpenClaw Setup Guide →

model-entity-validator

Scanned
npx machina-cli add skill smicolon/ai-kit/model-entity-validator --openclaw
Files (1)
SKILL.md
7.8 KB

Model/Entity Validator

Enforces Smicolon's BaseModel inheritance pattern for all Django models.

Activation Triggers

This skill activates when:

  • Creating new model files
  • Modifying existing models
  • Mentioning "model", "database", "schema", "table"
  • Writing class inheriting from models.Model
  • Running migrations
  • Discussing data structure

Core Principle: BaseModel Inheritance

NEVER repeat UUID/timestamp fields. All models inherit from BaseModel.

# ❌ WRONG - Repeating fields
class User(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_deleted = models.BooleanField(default=False)
    email = models.EmailField()

# ✅ CORRECT - Inherit from BaseModel
import core.models as _core_models

class User(_core_models.BaseModel):
    email = models.EmailField(unique=True)

Validation Process

Step 1: Check if BaseModel Exists

Before any action, check if the project has a BaseModel:

# Search for BaseModel in (in order):
# 1. core/models.py
# 2. shared/models.py
# 3. common/models.py
# 4. {app}/base.py

If BaseModel found: Suggest inheritance (Step 2a) If BaseModel NOT found: Create BaseModel first (Step 2b)

Step 2a: BaseModel Exists - Suggest Inheritance

When seeing:

class Product(models.Model):
    name = models.CharField(max_length=255)

Fix to:

import core.models as _core_models

class Product(_core_models.BaseModel):
    """Product model - inherits id, timestamps, soft delete from BaseModel."""

    name = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    class Meta:
        db_table = 'products'

Step 2b: BaseModel NOT Found - Create It First

If no BaseModel exists, create it:

# core/models.py
import uuid
from django.db import models

class BaseModel(models.Model):
    """
    Abstract base model providing:
    - UUID primary key
    - Automatic timestamps (created_at, updated_at)
    - Soft delete support (is_deleted)

    All models MUST inherit from this class.
    """

    id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_deleted = models.BooleanField(default=False)

    class Meta:
        abstract = True
        ordering = ['-created_at']

    def soft_delete(self) -> None:
        """Soft delete the record."""
        self.is_deleted = True
        self.save(update_fields=['is_deleted', 'updated_at'])

    def restore(self) -> None:
        """Restore a soft-deleted record."""
        self.is_deleted = False
        self.save(update_fields=['is_deleted', 'updated_at'])


class ActiveManager(models.Manager):
    """Manager that excludes soft-deleted records."""

    def get_queryset(self):
        return super().get_queryset().filter(is_deleted=False)

Report to developer:

BaseModel Created

Created core/models.py with BaseModel. All models should now inherit from it:

import core.models as _core_models

class YourModel(_core_models.BaseModel):
    # Your fields here - DO NOT add id, created_at, updated_at, is_deleted

Step 3: Detect Duplicate Fields

If seeing a model with explicit id/timestamp fields that inherits from BaseModel:

# ❌ WRONG - Duplicate fields
class User(_core_models.BaseModel):
    id = models.UUIDField(...)  # Already in BaseModel!
    created_at = models.DateTimeField(...)  # Already in BaseModel!
    email = models.EmailField()

Remove them:

# ✅ CORRECT - Only custom fields
class User(_core_models.BaseModel):
    email = models.EmailField(unique=True)

Step 4: Validate Indexes

Check if appropriate indexes exist:

class Product(_core_models.BaseModel):
    name = models.CharField(max_length=255)
    sku = models.CharField(max_length=100, unique=True)

    class Meta:
        db_table = 'products'
        indexes = [
            models.Index(fields=['sku']),  # Suggest for unique lookups
            models.Index(fields=['name']),  # Suggest for search
        ]

Complete Model Examples

Standard Model

# products/models.py
from django.db import models
import core.models as _core_models

class Product(_core_models.BaseModel):
    """Product model."""

    name = models.CharField(max_length=255)
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    sku = models.CharField(max_length=100, unique=True, db_index=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        db_table = 'products'
        verbose_name = 'Product'
        verbose_name_plural = 'Products'
        indexes = [
            models.Index(fields=['name']),
            models.Index(fields=['is_active', 'is_deleted']),
        ]

    def __str__(self):
        return self.name

User Model (with AbstractUser)

# users/models.py
import uuid
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    """
    Custom User model with UUID and timestamps.

    Note: For User, override AbstractUser directly since it has its own
    ID handling. Add the standard fields manually.
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_deleted = models.BooleanField(default=False)

    email = models.EmailField(unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        db_table = 'users'

Through Model (Many-to-Many)

# users/models.py
import core.models as _core_models

class UserOrganization(_core_models.BaseModel):
    """Through model for user-organization relationship."""

    user = models.ForeignKey('User', on_delete=models.CASCADE)
    organization = models.ForeignKey('Organization', on_delete=models.CASCADE)
    role = models.CharField(max_length=50)

    class Meta:
        db_table = 'user_organizations'
        unique_together = [['user', 'organization']]

Custom Managers

BaseModel provides soft delete. Add a custom manager for convenience:

import core.models as _core_models

class Product(_core_models.BaseModel):
    name = models.CharField(max_length=255)

    objects = models.Manager()  # All records
    active = _core_models.ActiveManager()  # Excludes soft-deleted

# Usage
Product.active.all()  # Only non-deleted
Product.objects.all()  # All including deleted

Validation Checklist

When reviewing a model, check:

  1. ✅ Inherits from BaseModel (or has explicit required fields for special cases)
  2. ✅ Does NOT duplicate fields from BaseModel
  3. ✅ Uses absolute import: import core.models as _core_models
  4. ✅ Has appropriate db_table name (singular or plural, snake_case)
  5. ✅ Has indexes on frequently queried fields
  6. ✅ Foreign keys have on_delete specified
  7. ✅ Has docstring explaining the model
  8. ✅ Has __str__ method for admin display

Behavior

Proactive enforcement:

  • Check if BaseModel exists FIRST
  • Suggest inheritance instead of field duplication
  • Remove duplicate fields automatically
  • Create BaseModel if missing
  • Explain WHY inheritance is better

Never:

  • Add duplicate id/timestamp fields to models
  • Let developers repeat base fields
  • Ignore existing BaseModel in the project

Source

git clone https://github.com/smicolon/ai-kit/blob/main/packs/django/skills/model-entity-validator/SKILL.mdView on GitHub

Overview

Model/Entity Validator enforces Smicolon's BaseModel inheritance pattern for Django models. It ensures every model inherits from BaseModel, avoids repeating UUID/timestamp fields, and guides creation or discovery of a BaseModel when it's missing. This validation is relevant during model creation, modification, migrations, and discussions about data structure.

How This Skill Works

It scans the codebase for an existing BaseModel (in core/models.py, shared/models.py, common/models.py, or {app}/base.py). If BaseModel exists, it recommends inheriting from _core_models.BaseModel (e.g., class Product(_core_models.BaseModel)). If BaseModel is not present, it provides a concrete BaseModel definition and guidance to create core/models.py first, then migrate models to inherit, while also detecting and flagging duplicate fields in models that still declare id or timestamp fields.

When to Use It

  • Creating a new model file
  • Modifying an existing model
  • Writing a class that inherits from models.Model
  • Running migrations
  • Defining or discussing the database schema or table structure

Quick Start

  1. Step 1: Search your project for an existing BaseModel (core/models.py, shared/models.py, common/models.py, or {app}/base.py).
  2. Step 2: If BaseModel exists, update new models to inherit from _core_models.BaseModel and remove duplicate fields.
  3. Step 3: If BaseModel does not exist, implement the BaseModel in core/models.py as shown and then migrate existing models to inherit it.

Best Practices

  • Always inherit from BaseModel; do not redefine id, created_at, updated_at, or is_deleted in child models.
  • Verify a BaseModel exists before adding new models; if not, create it first using the provided template.
  • Inherit using the alias import pattern: import core.models as _core_models; class MyModel(_core_models.BaseModel): ...
  • When creating BaseModel, include a UUID primary key, automatic timestamps, a soft delete flag, and an ActiveManager to exclude soft-deleted records.
  • Avoid duplicating fields and check for duplicate id/created_at/updated_at definitions when refactoring; rely on BaseModel for these fields.

Example Use Cases

  • ❌ Wrong: class User(models.Model): id = models.UUIDField(...); created_at = models.DateTimeField(...); ... ✅ Right: import core.models as _core_models; class User(_core_models.BaseModel): email = models.EmailField(unique=True)
  • BaseModel exists: core/models.py defines BaseModel; convert new models to inherit from _core_models.BaseModel instead of models.Model
  • Example: class Product(_core_models.BaseModel): name = models.CharField(max_length=255); price = models.DecimalField(...); class Meta: db_table = 'products'
  • When BaseModel is present, avoid redefining id/created_at in the child model; ensure only your domain fields remain in the class
  • If you see a model inheriting BaseModel but also declaring id or created_at, remove duplicates and rely on BaseModel for those fields

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers