Saleor Django Schema Migration
saleor/saleor
Generates and splits Django schema migrations for Saleor with manage.py makemigrations, enforcing one new model or one field change per migration file.
Structure Django models with proper Meta classes, verbose names, and optimized indexes.
$ npx skills add dvf/opinionated-django --skill dj-models -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install dvf/opinionated-django dj-models --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/dj-models .claude/skills/dj-models && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "dj-models" agent skill from https://github.com/dvf/opinionated-django/tree/master/skills/dj-models into .claude/skills/dj-models/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dj-models", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/dvf/opinionated-django/tree/master/skills/dj-modelsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add dvf/opinionated-django --skill dj-models -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install dvf/opinionated-django dj-models --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/dj-models .agents/skills/dj-models && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "dj-models" agent skill from https://github.com/dvf/opinionated-django/tree/master/skills/dj-models into .agents/skills/dj-models/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dj-models", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add dvf/opinionated-django --skill dj-models -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install dvf/opinionated-django dj-models --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/dj-models .cursor/skills/dj-models && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "dj-models" agent skill from https://github.com/dvf/opinionated-django/tree/master/skills/dj-models into .cursor/skills/dj-models/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dj-models", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/dvf/opinionated-django.git --path skills/dj-models--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add dvf/opinionated-django --skill dj-models -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install dvf/opinionated-django dj-models --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/dj-models .gemini/skills/dj-models && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "dj-models" agent skill from https://github.com/dvf/opinionated-django/tree/master/skills/dj-models into .gemini/skills/dj-models/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dj-models", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install dvf/opinionated-django dj-modelsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add dvf/opinionated-django --skill dj-models -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/dj-models .github/skills/dj-models && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "dj-models" agent skill from https://github.com/dvf/opinionated-django/tree/master/skills/dj-models into .github/skills/dj-models/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dj-models", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add dvf/opinionated-django --skill dj-models -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install dvf/opinionated-django dj-models --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/dj-models .opencode/skills/dj-models && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "dj-models" agent skill from https://github.com/dvf/opinionated-django/tree/master/skills/dj-models into .opencode/skills/dj-models/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "dj-models", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
dj-modelsStructure Django models with proper Meta classes, verbose names, and optimized indexes.
Dj Models is an agent skill from dvf/opinionated-django. Structure Django models with proper Meta classes, verbose names, and optimized indexes. Use when creating or reviewing Django models to ensure consistent ordering, correct verbosename/verbosenameplural, and database indexes aligned to actual query patterns. Also registers every model in the admin with a clean, fast-loading configuration.
Its SKILL.md is about 4.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Backend & APIs, covering Backend development. It works with Django. The repository describes itself as: An opinionated Django project with Repository pattern, Pydantic DTOs, svcs DI, and Stripe-style ULID IDs. The licence is MIT.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit f17fc2d. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
ReadWriteEditBashGrepGlobFrom allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
uvFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use uv, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Dj Models loads about 4.9k tokens when it runs. Until then it costs about 88 tokens; SKILL.md has 1,473 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
allowed-tools: Read, Write, Edit, Bash, Grep, GlobAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from dvf/opinionated-django at commit f17fc2d, republished under its MIT licence (© dvf). 1,473 words, ~4,866 tokens.
.claude/skills/dj-models/SKILL.md (or your agent's skills folder).You are defining or restructuring a Django model in an opinionated, fully type-safe Django project. Every convention below is mandatory. Do not deviate.
Read the model file being created or modified, plus:
src/project/ids.py — existing ID prefixessrc/<app>/admin.py — existing admin registrationsEvery model follows this exact ordering of members:
from typing import ClassVar
from django.db import models
from project.ids import generate_xxx_id
class MyEntity(models.Model):
# 1. Meta — ALWAYS first, before any field
class Meta:
verbose_name = "my entity"
verbose_name_plural = "my entities"
indexes = [
models.Index(fields=["-created_at"], name="idx_%(class)s_recent"),
]
constraints = [
models.UniqueConstraint(fields=["slug"], name="uq_%(class)s_slug"),
]
# 2. ClassVar prefix
__prefix__: ClassVar[str] = "xxx"
# 3. Identifiers — primary key, slugs, external refs
id = models.CharField(
max_length=64, primary_key=True, default=generate_xxx_id, editable=False
)
slug = models.SlugField(max_length=255)
# 4. Time fields — created, updated, any dates/datetimes
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# 5. Workflow / status / state (if applicable)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="draft")
# 6. Everything else — domain fields
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
# 7. Relations — ForeignKey, OneToOne, ManyToMany (always last among fields)
category = models.ForeignKey("categories.Category", on_delete=models.CASCADE)
# 8. __str__ — only if useful, and the only method allowed
def __str__(self) -> str:
return self.nameclass Meta is always the first thing inside the model body — before __prefix__, before the primary key, before any field. This is non-negotiable. It puts the most important structural information (naming, indexes, ordering, constraints) at the top where it's immediately visible.
The ordering inside Meta itself:
verbose_name and verbose_name_pluralindexesconstraints (unique constraints, check constraints)ordering, abstract, etc.)verbose_name and verbose_name_pluralEvery model's Meta must include both:
class Meta:
verbose_name = "order item"
verbose_name_plural = "order items""categorys", "order items" → "order itemss")verbose_name should read naturally in admin headers and log messagesFields are grouped by role, in this order:
created_at, updated_at, published_at, any date or datetimestatus, stage, is_active, is_published (skip if the model has no lifecycle)name, description, price, quantity, etc.ForeignKey, OneToOneField, ManyToManyField — always last among fieldsThis ordering makes scanning a model top-to-bottom predictable: "what is it, when was it, where is it in its lifecycle, what does it contain, what does it relate to."
All uniqueness and constraints are declared in Meta.constraints — never use unique=True on individual fields. This keeps all structural rules in one place, right at the top of the model.
class Meta:
verbose_name = "product"
verbose_name_plural = "products"
indexes = [
models.Index(fields=["-created_at"], name="idx_%(class)s_recent"),
]
constraints = [
models.UniqueConstraint(fields=["sku"], name="uq_%(class)s_sku"),
models.UniqueConstraint(fields=["store", "slug"], name="uq_%(class)s_store_slug"),
models.CheckConstraint(check=models.Q(price__gte=0), name="ck_%(class)s_price_pos"),
]Constraint naming convention:
uq_%(class)s_<short_description>ck_%(class)s_<short_description>UniqueConstraint is strictly more powerful than unique=True — it supports multi-column uniqueness, conditional uniqueness (condition=), and naming. Use it exclusively.
verbose_name and help_textAny field whose name is more than one word (joined by underscores) should have an explicit verbose_name so it reads cleanly in the admin:
price_at_purchase = models.DecimalField(
verbose_name="price at purchase",
max_digits=10,
decimal_places=2,
)Any field whose purpose is not immediately obvious from its name needs help_text. This shows up in the admin form below the field and serves as inline documentation:
idempotency_key = models.CharField(
max_length=255,
help_text="Client-generated key to prevent duplicate order submissions.",
)
retention_days = models.IntegerField(
verbose_name="retention days",
default=90,
help_text="Number of days to retain this record before archival.",
)Rules:
name, price, status) don't need a verbose_name — Django infers it fineprice_at_purchase, is_published, created_by) always get an explicit verbose_namehelp_text — if a new developer would need to ask "what is this?", add ithelp_text to one sentence, written for someone reading the admin formAll indexes are declared in Meta.indexes — never use db_index=True on individual fields. Centralizing indexes makes them reviewable at a glance and enables composite indexes that db_index=True cannot express.
class Meta:
verbose_name = "order"
verbose_name_plural = "orders"
indexes = [
models.Index(fields=["customer", "created_at"], name="idx_%(class)s_cust_created"),
models.Index(fields=["status", "-created_at"], name="idx_%(class)s_status_recent"),
]Index naming convention: idx_%(class)s_<short_description> — Django interpolates %(class)s to the lowercased model name, keeping names unique across models.
Don't index speculatively. Read the repository that queries this model and index for the queries that actually exist:
fields=["status", "-created_at"]ForeignKey fields, but if you always filter the FK with another column, replace it with a composite: fields=["order", "product"]- prefix: fields=["-created_at"] for queries that ORDER BY created_at DESCinclude (Postgres): models.Index(fields=["status"], include=["total"], name="idx_%(class)s_status_cov")condition: models.Index(fields=["created_at"], condition=models.Q(status="pending"), name="idx_%(class)s_pending")ForeignKey and UniqueConstraint. Don't add a redundant single-column index for those.Models contain ZERO business logic:
save() overrides__str__ is the only method allowed — and only if it adds value (skip it if the default ModelName object (pk) is fine)Every model gets registered in src/<app>/admin.py with a clean, fast-loading configuration. The admin should be aesthetic — well-organized, readable, and snappy even on large tables.
from django.contrib import admin
from .models.order import Order, OrderItem
class OrderItemInline(admin.TabularInline):
model = OrderItem
fields = ("id", "product", "quantity", "price_at_purchase")
readonly_fields = ("id",)
extra = 0
show_change_link = True
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ("id", "date", "total")
list_per_page = 25
search_fields = ("id",)
readonly_fields = ("id",)
ordering = ("-date",)
fieldsets = (
(None, {"fields": ("id", "date")}),
("Details", {"fields": ("total",)}),
)
inlines = [OrderItemInline]list_display — id first, then the most useful columns. Keep it to 4-6 fields max for readability.
list_per_page = 25 — default 100 is too slow on large tables. 25 keeps the admin snappy.
search_fields — always include id. Add name/title fields if they exist. Never search on unindexed columns.
readonly_fields — always include id (ULID PKs should never be edited). Add computed or auto-set fields.
ordering — explicit ordering so the admin doesn't rely on the default PK sort. Use -created_at or the most natural time field.
fieldsets — structure the change view semantically in three bands. Always follow this exact pattern:
None) fieldset — primary key and identity fields only (id, plus email / slug / natural key if the model has one). Renders with no collapsible header so identifiers are always visible at the top."Personal Info", "Details", "Role & Permissions", "Security", "Relations". Use "Details" as the catchall when no more specific name fits."Important Dates" (always last) — every datetime field goes here: created_at, updated_at, deleted_at, last_login, date_joined, etc. Title is exactly "Important Dates" (title case).fieldsets = (
(None, {"fields": ("id", "slug")}),
("Details", {"fields": ("name", "description", "status")}),
("Relations", {"fields": ("category",)}),
("Important Dates", {"fields": ("created_at", "updated_at")}),
)Rationale: identity at top (always visible), domain in the middle (named so it's scannable), dates at the bottom (rarely the thing you're editing, and separating them keeps the top fieldset focused on identity).
add_fieldsets — for models that support admin creation (User primarily), provide a minimal add_fieldsets with just the required fields for a new record:
add_fieldsets = (
(None, {
"classes": ("wide",),
"fields": ("email", "password1", "password2"),
}),
)Keeps the "Add" form focused — the full fieldsets is for editing, not creation.
list_select_related — specify FK fields shown in list_display to avoid N+1 queries: list_select_related = ("customer",)
raw_id_fields — use for any FK to a large table. The default dropdown loads every row: raw_id_fields = ("product",)
extra = 0 on inlines — never show empty inline forms by default.
show_change_link = True on inlines — lets you click through to the inline's own admin page.
TabularInline for child models on the parent's admin.
No list_filter on unindexed columns — filtering on unindexed columns causes full table scans.
autocomplete_fields — prefer over raw_id_fields when the related model has search_fields configured for a better UX: autocomplete_fields = ("customer",)
date_hierarchy — use on the primary date field if the model is time-series-like (orders, events, logs). Only use on indexed date fields.
Custom admin actions (the dropdown users select from on the changelist) MUST be thin adapters — never the home of business logic. The action receives a queryset, iterates it, delegates each row to a registered service via get(Service), and reports the result via self.message_user(request, ...).
from django.contrib import admin
from products.services.product import ProductService
from project.services import get
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
actions = ["fulfill_selected_action"]
@admin.action(description="Fulfill selected orders")
def fulfill_selected_action(self, request, queryset):
count = 0
for order in queryset:
get(ProductService).fulfill_order(order.id)
count += 1
self.message_user(request, f"Fulfilled {count} orders.")Rules:
<verb>_<noun>_action and listed in actions = [...]. The _action suffix distinguishes them from regular ModelAdmin methods at a glance.@admin.action(description="...") is mandatory — the description is what users see in the dropdown. No emojis, no ALL-CAPS shouting.get(Service).method(id). The action body MUST NOT contain ORM queries beyond iterating the incoming queryset, MUST NOT compute business logic, MUST NOT call external APIs directly.str IDs.self.message_user() with a result summary — count, or a short outcome description. This is what users see after running the action.from typing import ClassVar
from django.db import models
from products.models.product import Product
from project.ids import generate_itm_id, generate_ord_id
class Order(models.Model):
class Meta:
verbose_name = "order"
verbose_name_plural = "orders"
indexes = [
models.Index(fields=["-date"], name="idx_%(class)s_recent"),
models.Index(fields=["status", "-date"], name="idx_%(class)s_status_recent"),
]
constraints = [
models.UniqueConstraint(
fields=["idempotency_key"],
name="uq_%(class)s_idempotency",
),
]
__prefix__: ClassVar[str] = "ord"
# Identifiers
id = models.CharField(
max_length=64, primary_key=True, default=generate_ord_id, editable=False
)
idempotency_key = models.CharField(
verbose_name="idempotency key",
max_length=255,
help_text="Client-generated key to prevent duplicate order submissions.",
)
# Time
date = models.DateTimeField(auto_now_add=True)
# Status
status = models.CharField(max_length=20, default="pending")
# Domain
total = models.DecimalField(max_digits=12, decimal_places=2)
def __str__(self) -> str:
return f"Order {self.id} on {self.date}"
class OrderItem(models.Model):
class Meta:
verbose_name = "order item"
verbose_name_plural = "order items"
indexes = [
models.Index(fields=["order", "product"], name="idx_%(class)s_ord_prd"),
]
__prefix__: ClassVar[str] = "itm"
# Identifiers
id = models.CharField(
max_length=64, primary_key=True, default=generate_itm_id, editable=False
)
# Domain
quantity = models.PositiveIntegerField()
price_at_purchase = models.DecimalField(
verbose_name="price at purchase",
max_digits=10,
decimal_places=2,
help_text="Snapshot of the product price at the time the order was placed.",
)
# Relations
order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
def __str__(self) -> str:
return f"{self.quantity} x {self.product.name} (Order {self.order_id})" # type: ignore[attr-defined]Admin for the example above:
from django.contrib import admin
from .models.order import Order, OrderItem
class OrderItemInline(admin.TabularInline):
model = OrderItem
fields = ("id", "product", "quantity", "price_at_purchase")
readonly_fields = ("id",)
extra = 0
show_change_link = True
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ("id", "status", "date", "total")
list_per_page = 25
search_fields = ("id", "idempotency_key")
readonly_fields = ("id", "date")
ordering = ("-date",)
date_hierarchy = "date"
fieldsets = (
(None, {"fields": ("id", "date")}),
("Details", {"fields": ("status", "total", "idempotency_key")}),
)
inlines = [OrderItemInline]After creating or modifying models:
uv run python src/manage.py makemigrations && uv run python src/manage.py migrate
uv run ruff check src
uv run ruff format --check src
uv run pyrefly check src
uv run pytestAll must pass. Fix any issue rather than silencing it.
class Meta is the first thing inside the model bodyverbose_name and verbose_name_plural are set — never relying on Django's auto-pluralizationMeta.indexes — no db_index=True on fieldsMeta.constraints via UniqueConstraint — no unique=True on fieldsMeta.constraints where applicableverbose_namehelp_textsave(), signals, or computed properties__str__ only if useful, and the only method allowedlist_display, list_per_page = 25, search_fields, readonly_fields, ordering, fieldsetsfieldsets places id and timestamps in the first (untitled) fieldset at the top of the change viewraw_id_fields or autocomplete_fieldsextra = 0 and show_change_link = True© dvf, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/dj-models of dvf/opinionated-django.
Open the folder on GitHubat commit f17fc2d
Dj Models next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Dj Models this skilldvf/opinionated-django | 109 | — | ~4.9k | Automated safety check: Notes | MIT | |
| Saleor Django Schema Migrationsaleor/saleor | 23k | — | ~1k | Automated safety check: Pass | BSD-3-Clause | |
| Django Access Reviewgetsentry/skills | 1k | 3 repos | ~2.6k | Automated safety check: Notes | Apache-2.0 | |
| Silk Profilerbaserow/baserow | 6.1k | — | ~2.9k | Automated safety check: Pass | Custom licence | |
| Arch Wikiahmedemad3/arch-wiki | 249 | — | ~5.1k | Automated safety check: Pass | None | |
| Run Testsnetboxlabs/netbox-branching | 155 | — | ~1.2k | Automated safety check: Pass | Custom licence |
saleor/saleor
Generates and splits Django schema migrations for Saleor with manage.py makemigrations, enforcing one new model or one field change per migration file.
getsentry/skills
Django access control and IDOR security review. An agent skill from getsentry/skills.
baserow/baserow
Investigate backend performance using Django Silk profiling data.
ahmedemad3/arch-wiki
Scans any project codebase for new/changed modules, endpoints, middleware, infrastructure, Docker topologies, SQL queries, or permissions and updates docs/architecture/architecture.json.
netboxlabs/netbox-branching
Run the netboxbranching plugin's Django test suite against a local NetBox checkout.
aropan/clist
Add or change a CLIST Django management command, including arguments, resource selection, EventLog monitoring, or cron and Healthchecks wiring.
dvf/opinionated-django
Implement a Django feature following the opinionated architecture — prefixed ULID IDs, repository pattern, Pydantic DTOs, svcs service locator, project-scoped django-ninja API, Celery reliable…
dvf/opinionated-django
Use Stripe-style prefixed ULID primary keys (e.g. An agent skill from dvf/opinionated-django.
dvf/opinionated-django
Set up and write pytest tests for an op-django project — pytest-django configuration, two-sided Celery testing (patched dispatch sites + plain-function task bodies, never eager mode), freezegun for…
dvf/opinionated-django
Set up a Django project into the op-django layout so the architecture, signals, and settings skills have a foundation to build on.
dvf/opinionated-django
Structure Django business logic as plain services that receive their dependencies via constructor injection, and wire them through an svcs registry so they can be resolved anywhere — views, Celery…
dvf/opinionated-django
Add reliable signals (async side-effects via Celery) to a Django feature.
Works with
Categories
Structure Django models with proper Meta classes, verbose names, and optimized indexes. Dj Models is an agent skill from dvf/opinionated-django. Structure Django models with proper Meta classes, verbose names, and optimized indexes.
Dj Models fits situations like: reviewing Django models to ensure consistent ordering; correct verbosename/verbosenameplural; database indexes aligned to actual query patterns.
Run `npx skills add dvf/opinionated-django --skill dj-models -a claude-code`. Or copy the skill folder (skills/dj-models in dvf/opinionated-django) into .claude/skills/dj-models in your project. Claude Code loads it when a task matches its description.
Run `npx skills add dvf/opinionated-django --skill dj-models -a codex`. Or copy the skill folder (skills/dj-models in dvf/opinionated-django) into .agents/skills/dj-models in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add dvf/opinionated-django --skill dj-models -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/dj-models, .gemini/skills/dj-models, .github/skills/dj-models and .opencode/skills/dj-models in your project.
Going by SKILL.md and its folder, Dj Models needs the command-line tools its instructions call (uv). Our summary lists: Python 3. Its frontmatter pre-approves these tools: Read, Write, Edit, Bash, Grep, Glob.
SKILL.md contains no URLs. Its commands use uv, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Dj Models is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 4.9k tokens (SKILL.md is roughly 19k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Dj Models: Saleor Django Schema Migration (saleor/saleor, 23k stars), Django Access Review (getsentry/skills, 1k stars), Silk Profiler (baserow/baserow, 6.1k stars) and Arch Wiki (ahmedemad3/arch-wiki, 249 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
dvf (a GitHub user) maintains it in dvf/opinionated-django, which has 109 GitHub stars. The repository holds 9 skills in this directory. The repository was last updated on August 14, 2026.
Source: dvf/opinionated-django on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.