This skill should be used when the user asks to "create a model", "add a Django model", "create database table", "add entity", "define schema", or when writing class definitions inheriting from models.Model. Validates BaseModel inheritance pattern.
Scanned 5/28/2026
Install via CLI
openskills install smicolon/ai-kit---
name: model-entity-validator
description: This skill should be used when the user asks to "create a model", "add a Django model", "create database table", "add entity", "define schema", or when writing class definitions inheriting from models.Model. Validates BaseModel inheritance pattern.
---
# 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.
```python
# ❌ 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:
```python
# 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:
```python
class Product(models.Model):
name = models.CharField(max_length=255)
```
Fix to:
```python
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:
```python
# 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:
> ```python
> 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:
```python
# ❌ 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:
```python
# ✅ CORRECT - Only custom fields
class User(_core_models.BaseModel):
email = models.EmailField(unique=True)
```
### Step 4: Validate Indexes
Check if appropriate indexes exist:
```python
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
```python
# 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)
```python
# 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)
```python
# 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:
```python
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
No comments yet. Be the first to comment!