Agent skill

Dj Services

by dvf in 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…

MITAuto-check: notesBackend & APIs

Install Dj Services

skills CLI
$ npx skills add dvf/opinionated-django --skill dj-services -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install dvf/opinionated-django dj-services --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/dvf/opinionated-django.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/dj-services .claude/skills/dj-services && rm -rf skills-src

Use ~/.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/

Facts

Skill name
dj-services
GitHub stars
109
Token cost
~3k tokens
SKILL.md length
801 words
Files
1
Skills in repo
9
Repo updated
First seen
Licence
MIT

At a glance

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…

  • Adding a new service
  • SKILL.md covers Why svcs Instead of…, The Registry —…, Writing a Service and Resolving a Service, plus 3 more sections
  • Calls uv
  • Refactoring fat views

What it does

Dj Services is an agent skill from 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 tasks, management commands, tests. Use when adding a new service, refactoring fat views or model methods into a service, wiring a service into the registry, or explaining where business logic should live in this project.

Its SKILL.md is about 3k 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, Background jobs and Refactoring. 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.

When your agent uses it

  • Adding a new service
  • Refactoring fat views
  • Model methods into a service
  • Wiring a service into the registry

Example prompts

  • “/dj-services”

Requirements

  • Python 3
  • Pre-approved tools (allowed-tools): Read, Write, Edit, Bash, Grep, Glob

What it can do on your machine

Read from SKILL.md and the folder at commit f17fc2d. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves these tools, so the agent can use them without asking each time:

    • Read
    • Write
    • Edit
    • Bash
    • Grep
    • Glob

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • uv

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • svcs.hynek.me

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Dj Services loads about 3k tokens when it runs. Until then it costs about 107 tokens; SKILL.md has 801 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~107
When it runs · the whole SKILL.md, loaded when a task matches
~3k

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.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NotePre-approves every shell command (allowed-tools: Bash)SKILL.md
    allowed-tools: Read, Write, Edit, Bash, Grep, Glob

Automated 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.

SKILL.md

The full file from dvf/opinionated-django at commit f17fc2d, republished under its MIT licence (© dvf). 801 words, ~2,970 tokens.

Download SKILL.mdSave it as .claude/skills/dj-services/SKILL.md (or your agent's skills folder).
name
dj-services
description
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 tasks, management commands, tests. Use when adding a new service, refactoring fat views or model methods into a service, wiring a service into the registry, or explaining where business logic should live in this project.
allowed-tools
Read, Write, Edit, Bash, Grep, Glob

Services + svcs Dependency Injection

This project separates Django's framework concerns from business logic using a plain service layer, wired with the svcs service locator. The result:

  • Views are one-liners. They pull a wired service and call a method.
  • Services contain the business logic. They take repositories (and other services) via __init__, call methods on them, and return DTOs.
  • Services never import Django ORM or models. Every test can run without a database.
  • One registry, one get[T]() helper. The same call works in views, tasks, commands, anywhere.

Why svcs Instead of Module-Level Singletons or a Custom Container

  • svcs is a tiny, typed, well-maintained service locator — no metaclasses, no decorators, no framework coupling.
  • Factories are lazy: a service is constructed only when something asks for it.
  • Generic get[T](type[T]) -> T preserves types through IDE/type-checker inference.
  • Swapping an implementation in tests is a one-line factory override.
  • No import-order gymnastics: the registry is populated once at startup and then used by name.

The Registry — src/project/services.py

python
import svcs

from products.repositories.product import ProductRepository
from products.services.product import ProductService
from orders.repositories.order import OrderRepository
from orders.services.order import OrderService

registry = svcs.Registry()


# --- Services (factories pull repos from the container) ------------------
def _product_service_factory(container: svcs.Container) -> ProductService:
    repo = container.get(ProductRepository)
    return ProductService(repo)


def _order_service_factory(container: svcs.Container) -> OrderService:
    repo = container.get(OrderRepository)
    product_repo = container.get(ProductRepository)
    return OrderService(repo, product_repo)


def register_services(registry: svcs.Registry) -> None:
    """Register every real factory. Tests re-run this to restore overrides."""
    # --- Repositories -----------------------------------------------------
    registry.register_factory(ProductRepository, ProductRepository)
    registry.register_factory(OrderRepository, OrderRepository)
    # --- Services ---------------------------------------------------------
    registry.register_factory(ProductService, _product_service_factory)
    registry.register_factory(OrderService, _order_service_factory)


register_services(registry)


def get[T](service_type: type[T]) -> T:
    """Resolve a service from the registry. Works anywhere — views, tasks, commands, tests."""
    return svcs.Container(registry).get(service_type)

Patterns to follow:

  • All registrations live in register_services(registry). Called once at import; tests re-run it on teardown to restore any overridden factories through the public API.
  • Repositories register themselves. Use register_factory(Repo, Repo) — the class is its own factory because repositories take no constructor arguments.
  • Services register via a named factory. _<entity>_service_factory(container) resolves every dependency from the container and hands it to the service's __init__. No hidden imports, no module-level singletons.
  • Register in dependency order. Repos before services, lower-level services before higher-level ones. svcs doesn't enforce this, but it keeps the file readable.
  • One registry per project. Don't create ad-hoc registries — everything goes through project.services.registry.

Writing a Service

File: src/<app>/services/<entity>.py

python
from decimal import Decimal
from typing import List

from ..dtos.product import ProductDTO
from ..repositories.product import ProductRepository


class ProductService:
    def __init__(self, repo: ProductRepository):
        self.repo = repo

    def create_product(self, name: str, price: Decimal, stock: int) -> ProductDTO:
        return self.repo.create(name=name, price=price, stock=stock)

    def get_product(self, product_id: str) -> ProductDTO:
        return self.repo.get_by_id(product_id)

    def list_products(self) -> List[ProductDTO]:
        return self.repo.list_all()

Rules:

  • Dependencies come in through __init__. The service never instantiates its own repositories or services. If a service needs another service, pass it in.
  • Zero ORM. No .objects, no F() / Q(), no model imports, no select_related. All database access goes through a repository.
  • Every public method returns a DTO or list[DTO]. Never a model instance, never a queryset.
  • ID arguments are str. See the dj-prefixed-ulids skill.
  • Business rules live here. Validation, orchestration across repositories, invariant checks, error raising — all of it.
  • Services are stateless. They hold references to their dependencies and nothing else. No caches, no counters, no module-level state.
  • Raise plain exceptions. Use ValueError, PermissionError, domain-specific exceptions — not Http404 or anything Django-flavored. The view layer turns them into HTTP responses.
Cross-Entity Logic

When a service method touches more than one aggregate — e.g. creating an order that decrements product stock — inject both repositories and orchestrate them. Example from OrderService:

python
class OrderService:
    def __init__(self, repo: OrderRepository, product_repo: ProductRepository):
        self.repo = repo
        self.product_repo = product_repo

    def create_order(self, items: List[Dict[str, Any]]) -> OrderDTO:
        for item in items:
            product = self.product_repo.get_by_id(item["product_id"])
            if product.stock < item["quantity"]:
                raise ValueError(
                    f"Insufficient stock for product {product.name}: "
                    f"requested {item['quantity']}, available {product.stock}"
                )

        order = self.repo.create(items=items)

        for item in items:
            self.product_repo.decrement_stock(item["product_id"], item["quantity"])

        return order

Notes:

  • The service orchestrates two repositories but touches zero ORM.
  • If a multi-repo write needs atomicity, wrap it in with transaction.atomic(): — that's one of the very few django.db imports allowed in a service.
  • Never call another service from inside a service unless that service is explicitly injected. No hidden get(...) calls inside service methods.

Resolving a Service

Show full SKILL.md (326 more words)Show less
From a view (django-ninja)
python
from project.services import get
from project.types import AuthedRequest


@products_router.post("/", response={201: ProductDTO})
def create_product(request: AuthedRequest, payload: CreateProductIn):
    service = get(ProductService)
    return Status(201, service.create_product(**payload.dict()))

Annotating request as AuthedRequest (defined in src/project/types.py) makes the auth contract explicit and narrows request.user to a guaranteed-authenticated Django User. This is a typing contract, not runtime enforcement — auth is still expected to be wired via middleware or ninja's auth= parameter.

From a Celery task
python
from celery import shared_task

from project.services import get
from products.services.product import ProductService


@shared_task
def reprice_product(product_id: str, new_price: str) -> None:
    service = get(ProductService)
    service.update_price(product_id, Decimal(new_price))
From a management command
python
from django.core.management.base import BaseCommand

from project.services import get
from products.services.product import ProductService


class Command(BaseCommand):
    def handle(self, *args, **options):
        service = get(ProductService)
        for dto in service.list_products():
            self.stdout.write(dto.name)

The same get() call works in all three contexts because the registry is global and the container is cheap to construct.

Testing

Services are tested without a database. Pass in a MagicMock for each repository, configure its return values, and assert on the service's behavior.

python
from decimal import Decimal
from unittest.mock import MagicMock

import pytest

from products.dtos.product import ProductDTO
from products.services.product import ProductService


def test_create_product_delegates_to_repo():
    repo = MagicMock()
    expected = ProductDTO(id="prd_fake", name="Widget", price=Decimal("9.99"), stock=5)
    repo.create.return_value = expected

    service = ProductService(repo)
    result = service.create_product(name="Widget", price=Decimal("9.99"), stock=5)

    assert result is expected
    repo.create.assert_called_once_with(name="Widget", price=Decimal("9.99"), stock=5)


def test_create_order_rejects_insufficient_stock():
    order_repo = MagicMock()
    product_repo = MagicMock()
    product_repo.get_by_id.return_value = ProductDTO(
        id="prd_fake", name="Widget", price=Decimal("9.99"), stock=1
    )

    service = OrderService(order_repo, product_repo)

    with pytest.raises(ValueError, match="Insufficient stock"):
        service.create_order(items=[{"product_id": "prd_fake", "quantity": 5}])

    order_repo.create.assert_not_called()

This is the most important test layer — it proves the business logic is correct independently of Django, migrations, fixtures, or the database. If a service's tests need @pytest.mark.django_db, something has leaked: find the ORM call and push it back into a repository.

Overriding a Service in Tests

For integration tests that go through the API, override a factory to substitute a fake or a stub:

python
from project.services import register_services, registry
from products.services.product import ProductService


@pytest.fixture
def fake_product_service():
    fake = MagicMock(spec=ProductService)
    registry.register_factory(ProductService, lambda _: fake)
    yield fake
    register_services(registry)  # restore the real factories via public API

Common Mistakes

  • Importing models in a service. If you see from app.models import X, the service is doing ORM work. Move it to the repository.
  • Calling SomeRepository() inside a service method. Inject it via __init__ and hold the reference.
  • Returning querysets or model instances from a service. Always return DTOs.
  • Putting business logic in the view. The view should only decode input, call get(SomeService).method(...), and pass the result back.
  • Registering a service with register_factory(Service, Service). That only works for repositories because they take no arguments. Services need a factory that resolves their dependencies.
  • Reaching into request.user from the service. Pass the caller's identity as an explicit argument (user_id: str) so the service stays framework-agnostic.

Verify

  • Every service under src/<app>/services/ has an __init__ that takes its dependencies explicitly.
  • No file under src/<app>/services/ imports from django.db.models, <app>.models, or uses .objects.
  • Every service in the project is registered in src/project/services.py with a factory that resolves its dependencies from the container.
  • Service tests do not use @pytest.mark.django_db.
bash
uv run ruff check src
uv run pyrefly check src
uv run pytest

© dvf, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/dj-services of dvf/opinionated-django.

Open the folder on GitHubat commit f17fc2d

Compare with similar skills

Dj Services 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.

Dj Services compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Dj Services this skilldvf/opinionated-django109—~3kAutomated safety check: NotesMIT
Django Celery Expertvintasoftware/django-ai-plugins151—~1.2kAutomated safety check: PassNone
Sentry Python SDKgetsentry/sentry-for-ai268—~4.1kAutomated safety check: PassApache-2.0
Django Prodavila7/claude-code-templates32k8 repos~1.7kAutomated safety check: PassMIT
Django Celeryaffaan-m/ECC275k1 repos~3.3kAutomated safety check: PassMIT
Django Celeryaffaan-m/ECC275k—~240Automated safety check: PassMIT

Similar skills

  • Django Celery Expert

    vintasoftware/django-ai-plugins

    Expert Django Celery guidance for asynchronous task processing.

    151 GitHub stars~1.2k tokensUpdated 2 mo ago
    Backend & APIsAuto-check passed
  • Sentry Python SDK

    getsentry/sentry-for-ai

    Official

    Full Sentry SDK setup for Python. An agent skill from getsentry/sentry-for-ai.

    268 GitHub stars~4.1k tokensUpdated today
    Backend & APIsAuto-check passed
  • Django Pro

    davila7/claude-code-templates

    Master Django 5.x with async views, DRF, Celery, and Django Channels.

    32k GitHub starsUsed in 8 repos~1.7k tokens
    Backend & APIsAuto-check passed
  • Django Celery

    affaan-m/ECC

    Django + Celery async task patterns — configuration, task design, beat scheduling, retries, canvas workflows, monitoring, and testing.

    275k GitHub starsUsed in 1 repo~3.3k tokens
    Backend & APIsAuto-check passed
  • Django Celery

    affaan-m/ECC

    DjangoおよびCeleryを使用した非同期タスク処理。タスクキューイング、ワーカー管理、エラー処理、スケジューリング。Redis/RabbitMQ ブローカー統合。

    275k GitHub stars~240 tokensUpdated 3 days ago
    Backend & APIsAuto-check passed
  • Django Startup Time

    PostHog/posthog

    Official

    Keep heavy imports off the django.setup() path that every process (web, celery, temporal, migrate, shell, CI) pays for.

    40k GitHub stars~1.4k tokensUpdated today
    Backend & APIsAuto-check passed

More from dvf/opinionated-django

All 9 skills in this repo
  • Dj Architecture

    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…

    109 GitHub stars~4k tokensUpdated 1 mo ago
    Auto-check: notes
  • Dj Models

    dvf/opinionated-django

    Structure Django models with proper Meta classes, verbose names, and optimized indexes.

    109 GitHub stars~4.9k tokensUpdated 1 mo ago
    Auto-check: notes
  • Dj Prefixed Ulids

    dvf/opinionated-django

    Use Stripe-style prefixed ULID primary keys (e.g. An agent skill from dvf/opinionated-django.

    109 GitHub stars~1.4k tokensUpdated 1 mo ago
    Auto-check: notes
  • Dj Pytest

    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…

    109 GitHub stars~4.3k tokensUpdated 1 mo ago
    Auto-check: notes
  • Dj Scaffold

    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.

    109 GitHub stars~3.3k tokensUpdated 1 mo ago
    Auto-check: notes
  • Dj Signals

    dvf/opinionated-django

    Add reliable signals (async side-effects via Celery) to a Django feature.

    109 GitHub stars~1k tokensUpdated 1 mo ago
    Auto-check: notes

Works with

Categories

Questions about Dj Services

What does Dj Services do?

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…. Dj Services is an agent skill from 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 tasks, management commands, tests.

When should I use Dj Services?

Dj Services fits situations like: adding a new service; refactoring fat views; model methods into a service; wiring a service into the registry.

How do I install Dj Services in Claude Code?

Run `npx skills add dvf/opinionated-django --skill dj-services -a claude-code`. Or copy the skill folder (skills/dj-services in dvf/opinionated-django) into .claude/skills/dj-services in your project. Claude Code loads it when a task matches its description.

How do I install Dj Services in Codex?

Run `npx skills add dvf/opinionated-django --skill dj-services -a codex`. Or copy the skill folder (skills/dj-services in dvf/opinionated-django) into .agents/skills/dj-services in your project. Codex loads it when a task matches its description.

Can I use Dj Services in Cursor, Gemini CLI or GitHub Copilot?

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-services -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-services, .gemini/skills/dj-services, .github/skills/dj-services and .opencode/skills/dj-services in your project.

What does Dj Services need to run?

Going by SKILL.md and its folder, Dj Services 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.

Does Dj Services access the network?

SKILL.md names 1 domain. As links in the text: svcs.hynek.me. This is read from the text; nothing was executed.

Is Dj Services safe to install?

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.

What licence does Dj Services use?

Dj Services is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Dj Services use?

About 3k tokens (SKILL.md is roughly 12k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Dj Services?

Skills that share tags, products or a category with Dj Services: Django Celery Expert (vintasoftware/django-ai-plugins, 151 stars), Sentry Python SDK (getsentry/sentry-for-ai, 268 stars), Django Pro (davila7/claude-code-templates, 32k stars) and Django Celery (affaan-m/ECC, 275k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Dj Services?

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.