Codegen¶
Generate models, viewsets, serializers, and tests with AI.
Overview¶
Codegen generates boilerplate code that matches your project's patterns:
from aksara.ai import Codegen
gen = Codegen()
code = await gen.model("BlogPost with title, content, author FK")
print(code)
Quick Start¶
Generate a Model¶
from aksara.ai import Codegen
gen = Codegen()
# From description
model_code = await gen.model(
"UserProfile with bio, avatar URL, and user FK"
)
print(model_code)
# class UserProfile(Model):
# bio = fields.Text(null=True)
# avatar_url = fields.URL(null=True)
# user = fields.ForeignKey("User", on_delete=fields.CASCADE)
Generate a ViewSet¶
viewset_code = await gen.viewset(
model="UserProfile",
features=["pagination", "search", "filter_by_user"],
)
Generate Tests¶
Model Generation¶
Basic Model¶
code = await gen.model("Product with name, price, description")
# Output:
# class Product(Model):
# name = fields.String(max_length=200)
# price = fields.Decimal(max_digits=10, decimal_places=2)
# description = fields.Text(null=True)
#
# class Meta:
# table_name = "products"
With Relations¶
code = await gen.model(
"OrderItem with order FK, product FK, quantity, unit_price"
)
# Output:
# class OrderItem(Model):
# order = fields.ForeignKey("Order", on_delete=fields.CASCADE, related_name="items")
# product = fields.ForeignKey("Product", on_delete=fields.PROTECT)
# quantity = fields.Integer()
# unit_price = fields.Decimal(max_digits=10, decimal_places=2)
With Many-to-Many¶
code = await gen.model(
"Article with title, content, and tags M2M"
)
# Output:
# class Article(Model):
# title = fields.String(max_length=200)
# content = fields.Text()
# tags = fields.ManyToMany("Tag", related_name="articles")
With Constraints¶
code = await gen.model(
"User with email (unique), username (unique), password"
)
# Output:
# class User(Model):
# email = fields.Email(unique=True)
# username = fields.String(max_length=150, unique=True)
# password = fields.String(max_length=128)
With Indexes¶
code = await gen.model(
"Event with title, start_date, end_date, indexed by start_date"
)
# Output includes:
# class Meta:
# indexes = [
# {"fields": ["start_date"]},
# ]
ViewSet Generation¶
Basic ViewSet¶
code = await gen.viewset(model="Product")
# Output:
# class ProductViewSet(ModelViewSet):
# model = Product
# serializer_class = ProductSerializer
With Features¶
code = await gen.viewset(
model="Product",
features=[
"pagination",
"search",
"filtering",
"ordering",
],
)
# Output:
# class ProductViewSet(ModelViewSet):
# model = Product
# serializer_class = ProductSerializer
# pagination_class = PageNumberPagination
# search_fields = ["name", "description"]
# filterset_fields = ["category", "is_active"]
# ordering_fields = ["name", "price", "created_at"]
With Custom Actions¶
code = await gen.viewset(
model="Order",
features=["pagination"],
actions=["mark_shipped", "cancel", "refund"],
)
# Output includes:
# @action(detail=True, methods=["post"])
# async def mark_shipped(self, request, pk=None):
# order = await self.get_object()
# order.status = "shipped"
# await order.save()
# return Response({"status": "shipped"})
With Permissions¶
Serializer Generation¶
Basic Serializer¶
code = await gen.serializer(model="Product")
# Output:
# class ProductSerializer(ModelSerializer):
# class Meta:
# model = Product
# fields = ["id", "name", "price", "description", "created_at"]
With Nested Relations¶
code = await gen.serializer(
model="Order",
nested=["items", "customer"],
)
# Output:
# class OrderSerializer(ModelSerializer):
# items = OrderItemSerializer(many=True, read_only=True)
# customer = CustomerSerializer(read_only=True)
#
# class Meta:
# model = Order
# fields = ["id", "items", "customer", "total", "status"]
With Custom Fields¶
code = await gen.serializer(
model="User",
extra_fields=["full_name", "post_count"],
)
# Output includes:
# full_name = serializers.SerializerMethodField()
# post_count = serializers.Integer(read_only=True)
#
# def get_full_name(self, obj):
# return f"{obj.first_name} {obj.last_name}"
Test Generation¶
CRUD Tests¶
code = await gen.test(
model="Product",
test_types=["crud"],
)
# Output:
# class TestProduct(AksaraTestCase):
# async def test_create_product(self):
# product = await Product.objects.create(
# name="Test Product",
# price=Decimal("29.99"),
# )
# assert product.id is not None
#
# async def test_read_product(self):
# ...
#
# async def test_update_product(self):
# ...
#
# async def test_delete_product(self):
# ...
Validation Tests¶
code = await gen.test(
model="User",
test_types=["validation"],
)
# Output:
# async def test_email_required(self):
# with pytest.raises(ValidationError):
# await User.objects.create(username="test")
#
# async def test_email_unique(self):
# await User.objects.create(email="test@example.com")
# with pytest.raises(IntegrityError):
# await User.objects.create(email="test@example.com")
API Tests¶
code = await gen.test(
model="Product",
test_types=["api"],
viewset="ProductViewSet",
)
# Output:
# async def test_list_products(self):
# response = await self.client.get("/api/products/")
# assert response.status_code == 200
#
# async def test_create_product(self):
# response = await self.client.post("/api/products/", {
# "name": "New Product",
# "price": "29.99",
# })
# assert response.status_code == 201
Migration Generation¶
From Model Changes¶
code = await gen.migration(
description="Add phone field to User",
)
# Output:
# class Migration:
# dependencies = [("myapp", "0004_previous")]
#
# operations = [
# AddField(
# model_name="user",
# name="phone",
# field=fields.String(max_length=20, null=True),
# ),
# ]
Data Migration¶
code = await gen.migration(
description="Populate user full_name from first_name and last_name",
migration_type="data",
)
# Output includes:
# async def forwards(apps, schema_editor):
# User = apps.get_model("myapp", "User")
# async for user in User.objects.all():
# user.full_name = f"{user.first_name} {user.last_name}"
# await user.save()
Context-Aware Generation¶
Using Project Context¶
from aksara.ai import Codegen, ContextEngine
context_engine = ContextEngine()
gen = Codegen()
# Gather existing patterns
context = await context_engine.gather("Model patterns")
# Generate matching existing style
code = await gen.model(
"Comment with text, author, post",
context=context,
)
Style Matching¶
Codegen analyzes your existing code to match:
- Field naming conventions
- Import styles
- Docstring formats
- Meta class patterns
CLI Usage¶
Generate Model¶
Generate ViewSet¶
Generate Test¶
Interactive Mode¶
aksara ai generate
? What would you like to generate? Model
? Describe the model: BlogPost with title, slug, content, author FK, tags M2M
? Add any constraints? title unique, slug unique
? Generate migration? Yes
✓ Generated model in models.py
✓ Generated migration 0005_create_blogpost.py
Output Options¶
Return Code String¶
Write to File¶
Return Structured¶
result = await gen.model(
"Product",
return_structured=True,
)
print(result.code) # The code
print(result.imports) # Required imports
print(result.file_path) # Suggested file path
Configuration¶
# settings.py
AKSARA = {
"AI_CODEGEN": {
# Style preferences
"docstring_style": "google", # or "numpy", "sphinx"
"import_style": "absolute", # or "relative"
# Defaults
"default_field_null": False,
"include_timestamps": True, # Add created_at, updated_at
"include_uuid_pk": True, # Use UUID primary keys
# Output
"models_file": "models.py",
"viewsets_file": "viewsets.py",
"tests_dir": "tests/",
},
}
Best Practices¶
1. Be Specific¶
# Less specific
await gen.model("User")
# More specific
await gen.model(
"User with email (unique, required), "
"username (unique, max 150), "
"password (required, max 128), "
"is_active (boolean, default true)"
)
2. Review Generated Code¶
Always review generated code before using:
3. Use Context¶
# Better results with context
context = await context_engine.gather("existing models")
code = await gen.model("...", context=context)
Related Documentation¶
- AI Mode Overview
- Context Engine
- Patch Engine
- Models — Model reference
- ViewSets — ViewSet reference