oh-my-customcode 0.36.2 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +47 -2
- package/dist/index.js +44 -0
- package/package.json +1 -1
- package/templates/.claude/agents/arch-documenter.md +4 -1
- package/templates/.claude/agents/arch-speckit-agent.md +15 -0
- package/templates/.claude/agents/be-django-expert.md +1 -0
- package/templates/.claude/agents/be-express-expert.md +1 -0
- package/templates/.claude/agents/be-fastapi-expert.md +1 -0
- package/templates/.claude/agents/be-go-backend-expert.md +1 -0
- package/templates/.claude/agents/be-nestjs-expert.md +1 -0
- package/templates/.claude/agents/be-springboot-expert.md +1 -0
- package/templates/.claude/agents/db-postgres-expert.md +1 -0
- package/templates/.claude/agents/db-redis-expert.md +1 -0
- package/templates/.claude/agents/db-supabase-expert.md +1 -0
- package/templates/.claude/agents/de-airflow-expert.md +1 -0
- package/templates/.claude/agents/de-dbt-expert.md +1 -0
- package/templates/.claude/agents/de-kafka-expert.md +1 -0
- package/templates/.claude/agents/de-pipeline-expert.md +1 -0
- package/templates/.claude/agents/de-snowflake-expert.md +1 -0
- package/templates/.claude/agents/de-spark-expert.md +1 -0
- package/templates/.claude/agents/fe-flutter-agent.md +1 -0
- package/templates/.claude/agents/fe-svelte-agent.md +1 -0
- package/templates/.claude/agents/fe-vercel-agent.md +1 -0
- package/templates/.claude/agents/fe-vuejs-agent.md +1 -0
- package/templates/.claude/agents/infra-aws-expert.md +1 -0
- package/templates/.claude/agents/infra-docker-expert.md +1 -0
- package/templates/.claude/agents/lang-golang-expert.md +1 -0
- package/templates/.claude/agents/lang-java21-expert.md +3 -0
- package/templates/.claude/agents/lang-kotlin-expert.md +1 -0
- package/templates/.claude/agents/lang-python-expert.md +1 -0
- package/templates/.claude/agents/lang-rust-expert.md +1 -0
- package/templates/.claude/agents/lang-typescript-expert.md +1 -0
- package/templates/.claude/agents/mgr-claude-code-bible.md +1 -2
- package/templates/.claude/agents/mgr-creator.md +1 -0
- package/templates/.claude/agents/mgr-gitnerd.md +1 -0
- package/templates/.claude/agents/mgr-sauron.md +5 -2
- package/templates/.claude/agents/mgr-supplier.md +1 -3
- package/templates/.claude/agents/mgr-updater.md +1 -0
- package/templates/.claude/agents/qa-engineer.md +1 -0
- package/templates/.claude/agents/qa-planner.md +4 -1
- package/templates/.claude/agents/qa-writer.md +1 -1
- package/templates/.claude/agents/sec-codeql-expert.md +4 -2
- package/templates/.claude/agents/sys-memory-keeper.md +30 -0
- package/templates/.claude/agents/sys-naggy.md +36 -2
- package/templates/.claude/agents/tool-bun-expert.md +1 -1
- package/templates/.claude/agents/tool-npm-expert.md +1 -1
- package/templates/.claude/agents/tool-optimizer.md +1 -2
- package/templates/.claude/hooks/hooks.json +2 -2
- package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +10 -0
- package/templates/.claude/hooks/scripts/content-hash-validator.sh +2 -3
- package/templates/.claude/hooks/scripts/schema-validator.sh +15 -0
- package/templates/.claude/hooks/scripts/secret-filter.sh +31 -1
- package/templates/.claude/rules/MUST-agent-teams.md +0 -23
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +1 -13
- package/templates/.claude/skills/django-best-practices/SKILL.md +27 -134
- package/templates/.claude/skills/flutter-best-practices/SKILL.md +39 -146
- package/templates/.claude/skills/go-backend-best-practices/SKILL.md +29 -233
- package/templates/.claude/skills/java21-best-practices/SKILL.md +48 -163
- package/templates/CLAUDE.md.en +7 -65
- package/templates/CLAUDE.md.ko +7 -65
- package/templates/manifest.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: sys-naggy
|
|
3
3
|
description: Use when you need TODO list management and task tracking with proactive reminders, helping maintain project momentum by monitoring stale tasks and deadlines
|
|
4
4
|
model: sonnet
|
|
5
|
+
domain: universal
|
|
5
6
|
memory: local
|
|
6
7
|
effort: low
|
|
7
8
|
tools:
|
|
@@ -9,8 +10,6 @@ tools:
|
|
|
9
10
|
- Write
|
|
10
11
|
- Edit
|
|
11
12
|
- Grep
|
|
12
|
-
- Glob
|
|
13
|
-
- Bash
|
|
14
13
|
---
|
|
15
14
|
|
|
16
15
|
You are a task management specialist that proactively manages TODO items and reminds users of pending tasks.
|
|
@@ -31,6 +30,41 @@ You are a task management specialist that proactively manages TODO items and rem
|
|
|
31
30
|
| `sys-naggy:done <id>` | Mark complete |
|
|
32
31
|
| `sys-naggy:remind` | Show overdue tasks |
|
|
33
32
|
|
|
33
|
+
## Rule Pattern Detection
|
|
34
|
+
|
|
35
|
+
When sys-naggy detects recurring violations (3+ occurrences of the same rule ID across sessions), it proposes a rule patch:
|
|
36
|
+
|
|
37
|
+
### Detection Flow
|
|
38
|
+
|
|
39
|
+
1. Read violation history from native memory (`MEMORY.md` violations section)
|
|
40
|
+
2. Cross-reference with session compliance data (PPID-scoped `/tmp/.claude-session-compliance-*`)
|
|
41
|
+
3. Identify rules with 3+ violations across different sessions
|
|
42
|
+
4. Generate rule patch proposal as GitHub issue
|
|
43
|
+
|
|
44
|
+
### Proposal Format
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Title: [R016 Auto-Patch] R0XX: {weakness description}
|
|
48
|
+
Body:
|
|
49
|
+
## Violation Pattern
|
|
50
|
+
- Rule: R0XX ({rule name})
|
|
51
|
+
- Occurrences: {count} across {session_count} sessions
|
|
52
|
+
- Common trigger: {pattern description}
|
|
53
|
+
|
|
54
|
+
## Proposed Fix
|
|
55
|
+
{specific change to the rule file}
|
|
56
|
+
|
|
57
|
+
## Rationale
|
|
58
|
+
{why the current rule is insufficient}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Constraints
|
|
62
|
+
|
|
63
|
+
- sys-naggy proposes patches as GitHub issues — never auto-applies
|
|
64
|
+
- Minimum 3 occurrences before proposing (avoids noise)
|
|
65
|
+
- Maximum 1 proposal per rule per week (debounce)
|
|
66
|
+
- Proposals require human approval before implementation
|
|
67
|
+
|
|
34
68
|
## Behavior
|
|
35
69
|
|
|
36
70
|
Proactive but not annoying. Adapt reminder frequency to user response.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: tool-bun-expert
|
|
3
3
|
description: Use for Bun runtime development, bunfig.toml configuration, Bun test runner, fast bundling, and Node.js to Bun migrations
|
|
4
4
|
model: sonnet
|
|
5
|
+
domain: universal
|
|
5
6
|
memory: project
|
|
6
7
|
effort: medium
|
|
7
8
|
tools:
|
|
@@ -9,7 +10,6 @@ tools:
|
|
|
9
10
|
- Write
|
|
10
11
|
- Edit
|
|
11
12
|
- Grep
|
|
12
|
-
- Glob
|
|
13
13
|
- Bash
|
|
14
14
|
---
|
|
15
15
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: tool-npm-expert
|
|
3
3
|
description: Use for npm package publishing workflows, semantic versioning (major/minor/patch), package.json optimization, and dependency audits
|
|
4
4
|
model: sonnet
|
|
5
|
+
domain: universal
|
|
5
6
|
memory: project
|
|
6
7
|
effort: medium
|
|
7
8
|
skills:
|
|
@@ -13,7 +14,6 @@ tools:
|
|
|
13
14
|
- Write
|
|
14
15
|
- Edit
|
|
15
16
|
- Grep
|
|
16
|
-
- Glob
|
|
17
17
|
- Bash
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
name: tool-optimizer
|
|
3
3
|
description: Use for bundle size analysis, tree-shaking verification, performance profiling, dead code detection, and build optimization recommendations
|
|
4
4
|
model: sonnet
|
|
5
|
+
domain: universal
|
|
5
6
|
memory: project
|
|
6
7
|
effort: medium
|
|
7
8
|
skills:
|
|
@@ -10,8 +11,6 @@ skills:
|
|
|
10
11
|
- optimize-report
|
|
11
12
|
tools:
|
|
12
13
|
- Read
|
|
13
|
-
- Write
|
|
14
|
-
- Edit
|
|
15
14
|
- Grep
|
|
16
15
|
- Glob
|
|
17
16
|
- Bash
|
|
@@ -263,14 +263,14 @@
|
|
|
263
263
|
"description": "Store content hashes for Read operations — enables Edit staleness detection"
|
|
264
264
|
},
|
|
265
265
|
{
|
|
266
|
-
"matcher": "tool == \"Bash\" || tool == \"Read\"",
|
|
266
|
+
"matcher": "tool == \"Bash\" || tool == \"Read\" || tool == \"Grep\"",
|
|
267
267
|
"hooks": [
|
|
268
268
|
{
|
|
269
269
|
"type": "command",
|
|
270
270
|
"command": "bash .claude/hooks/scripts/secret-filter.sh"
|
|
271
271
|
}
|
|
272
272
|
],
|
|
273
|
-
"description": "Detect potential secrets in Bash/Read output — advisory warning only"
|
|
273
|
+
"description": "Detect potential secrets in Bash/Read/Grep output — advisory warning only"
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
"matcher": "tool == \"Edit\" || tool == \"Write\" || tool == \"Bash\" || tool == \"Agent\"",
|
|
@@ -8,6 +8,16 @@ set -euo pipefail
|
|
|
8
8
|
|
|
9
9
|
input=$(cat)
|
|
10
10
|
|
|
11
|
+
# Skip if Agent Teams is not available
|
|
12
|
+
ENV_STATUS="/tmp/.claude-env-status-${PPID}"
|
|
13
|
+
if [ -f "$ENV_STATUS" ]; then
|
|
14
|
+
teams_status=$(grep "agent_teams=" "$ENV_STATUS" 2>/dev/null | cut -d= -f2 || echo "unknown")
|
|
15
|
+
if [ "$teams_status" != "enabled" ]; then
|
|
16
|
+
echo "$input"
|
|
17
|
+
exit 0
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
11
21
|
# Extract task info from input
|
|
12
22
|
agent_type=$(echo "$input" | jq -r '.tool_input.subagent_type // "unknown"')
|
|
13
23
|
prompt_preview=$(echo "$input" | jq -r '.tool_input.description // ""' | head -c 60)
|
|
@@ -18,10 +18,9 @@ case "$tool_name" in
|
|
|
18
18
|
"Read")
|
|
19
19
|
# Store content hash for the file that was just read
|
|
20
20
|
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
|
|
21
|
-
output=$(echo "$input" | jq -r '.tool_output.output // ""')
|
|
22
21
|
|
|
23
|
-
if [ -n "$file_path" ] && [ -
|
|
24
|
-
content_hash=$(
|
|
22
|
+
if [ -n "$file_path" ] && [ -f "$file_path" ]; then
|
|
23
|
+
content_hash=$(md5 -q "$file_path" 2>/dev/null || md5sum "$file_path" 2>/dev/null | cut -d' ' -f1 || echo "unknown")
|
|
25
24
|
timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
26
25
|
|
|
27
26
|
# Store hash entry (overwrite previous for same file)
|
|
@@ -72,6 +72,21 @@ case "$tool_name" in
|
|
|
72
72
|
if echo "$command" | grep -qE 'mkfs\.'; then
|
|
73
73
|
warnings+=("[Schema] Bash: filesystem format command detected")
|
|
74
74
|
fi
|
|
75
|
+
# Remote code execution via pipe
|
|
76
|
+
if echo "$command" | grep -qE 'curl\s+.*\|\s*(ba)?sh'; then
|
|
77
|
+
warnings+=("[Schema] Bash: remote code execution pattern (curl | bash) detected")
|
|
78
|
+
fi
|
|
79
|
+
if echo "$command" | grep -qE 'wget\s+.*\|\s*(ba)?sh'; then
|
|
80
|
+
warnings+=("[Schema] Bash: remote code execution pattern (wget | sh) detected")
|
|
81
|
+
fi
|
|
82
|
+
# Dynamic code execution
|
|
83
|
+
if echo "$command" | grep -qE 'eval\s+\$\('; then
|
|
84
|
+
warnings+=("[Schema] Bash: dynamic code execution (eval) detected")
|
|
85
|
+
fi
|
|
86
|
+
# Broad permission grant
|
|
87
|
+
if echo "$command" | grep -qE 'chmod\s+777'; then
|
|
88
|
+
warnings+=("[Schema] Bash: broad permission grant (chmod 777) detected")
|
|
89
|
+
fi
|
|
75
90
|
;;
|
|
76
91
|
esac
|
|
77
92
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Secret Output Filter Hook — Detect potential secrets in tool output
|
|
3
|
-
# Trigger: PostToolUse on Bash, Read
|
|
3
|
+
# Trigger: PostToolUse on Bash, Read, Grep
|
|
4
4
|
# Purpose: Advisory warning when potential secrets detected in output
|
|
5
5
|
# Protocol: stdin JSON -> scan -> stdout pass-through
|
|
6
6
|
# Always exits 0 (advisory only, never blocks)
|
|
@@ -58,6 +58,36 @@ if echo "$output" | grep -qE 'gho_[a-zA-Z0-9]{36}'; then
|
|
|
58
58
|
detected=true
|
|
59
59
|
fi
|
|
60
60
|
|
|
61
|
+
# GitHub Fine-Grained PAT
|
|
62
|
+
if echo "$output" | grep -qE 'github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}'; then
|
|
63
|
+
echo "[Security] Potential GitHub Fine-Grained PAT detected in ${tool_name} output" >&2
|
|
64
|
+
detected=true
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# GitHub Actions Token
|
|
68
|
+
if echo "$output" | grep -qE 'ghs_[a-zA-Z0-9]{36}'; then
|
|
69
|
+
echo "[Security] Potential GitHub Actions token detected in ${tool_name} output" >&2
|
|
70
|
+
detected=true
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# npm Token
|
|
74
|
+
if echo "$output" | grep -qE 'npm_[a-zA-Z0-9]{36}'; then
|
|
75
|
+
echo "[Security] Potential npm token detected in ${tool_name} output" >&2
|
|
76
|
+
detected=true
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Slack Token
|
|
80
|
+
if echo "$output" | grep -qE 'xox[bsarp]-[a-zA-Z0-9-]{10,}'; then
|
|
81
|
+
echo "[Security] Potential Slack token detected in ${tool_name} output" >&2
|
|
82
|
+
detected=true
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Docker Hub PAT
|
|
86
|
+
if echo "$output" | grep -qE 'dckr_pat_[a-zA-Z0-9_-]{20,}'; then
|
|
87
|
+
echo "[Security] Potential Docker Hub PAT detected in ${tool_name} output" >&2
|
|
88
|
+
detected=true
|
|
89
|
+
fi
|
|
90
|
+
|
|
61
91
|
if [ "$detected" = true ]; then
|
|
62
92
|
echo "[Security] Review output carefully — do NOT commit or expose secrets" >&2
|
|
63
93
|
fi
|
|
@@ -104,17 +104,6 @@ All members must be spawned in a single message. Partial spawning needs correcti
|
|
|
104
104
|
implementer → fixes → SendMessage(reviewer, "fixed")
|
|
105
105
|
reviewer → re-reviews → done
|
|
106
106
|
|
|
107
|
-
❌ WRONG: Multi-expert task without coordination
|
|
108
|
-
Agent(lang-typescript-expert) → "Implement frontend"
|
|
109
|
-
Agent(be-express-expert) → "Implement API"
|
|
110
|
-
(no shared state, results manually combined)
|
|
111
|
-
|
|
112
|
-
✓ CORRECT: Agent Teams for cross-domain work
|
|
113
|
-
TeamCreate("fullstack")
|
|
114
|
-
Agent(frontend-dev) + Agent(backend-dev) → team members
|
|
115
|
-
Shared TaskList for interface contracts
|
|
116
|
-
SendMessage for API schema coordination
|
|
117
|
-
|
|
118
107
|
❌ WRONG: Spawning team members one at a time
|
|
119
108
|
TeamCreate("research-team")
|
|
120
109
|
Message 1: Agent(researcher-1) → Analysis 1 (only 1/3 spawned)
|
|
@@ -127,18 +116,6 @@ All members must be spawned in a single message. Partial spawning needs correcti
|
|
|
127
116
|
Agent(researcher-1) → Analysis 1 ┐
|
|
128
117
|
Agent(researcher-2) → Analysis 2 ├─ ALL spawned together
|
|
129
118
|
Agent(researcher-3) → Analysis 3 ┘
|
|
130
|
-
|
|
131
|
-
❌ WRONG: Multi-issue batch as independent agents
|
|
132
|
-
Agent(general-purpose) → "Fix issue #1"
|
|
133
|
-
Agent(general-purpose) → "Fix issue #2"
|
|
134
|
-
Agent(general-purpose) → "Fix issue #3"
|
|
135
|
-
(no coordination, no shared task tracking)
|
|
136
|
-
|
|
137
|
-
✓ CORRECT: Agent Teams for multi-issue batches
|
|
138
|
-
TeamCreate("release-fixes")
|
|
139
|
-
TaskCreate for each issue
|
|
140
|
-
Agent(fixer-1) + Agent(fixer-2) + Agent(fixer-3) → team members
|
|
141
|
-
Shared task list tracks progress across all issues
|
|
142
119
|
```
|
|
143
120
|
|
|
144
121
|
## Cost Guidelines
|
|
@@ -95,12 +95,6 @@ Main Conversation (orchestrator)
|
|
|
95
95
|
Main conversation → Agent(mgr-gitnerd) → git commit
|
|
96
96
|
Main conversation → Agent(mgr-gitnerd) → git push
|
|
97
97
|
|
|
98
|
-
❌ WRONG: Using general-purpose when specialist exists
|
|
99
|
-
Main conversation → Agent(general-purpose) → "Write Go code"
|
|
100
|
-
|
|
101
|
-
✓ CORRECT: Using the right specialist
|
|
102
|
-
Main conversation → Agent(lang-golang-expert) → "Write Go code"
|
|
103
|
-
|
|
104
98
|
❌ WRONG: Orchestrator creates files "just this once"
|
|
105
99
|
"It's just a small config file, I'll write it directly..."
|
|
106
100
|
|
|
@@ -110,15 +104,9 @@ Main Conversation (orchestrator)
|
|
|
110
104
|
❌ WRONG: Bundling git operations with file editing in non-gitnerd agent
|
|
111
105
|
Main conversation → Agent(general-purpose) → "git revert + edit file + git commit"
|
|
112
106
|
Main conversation → Agent(lang-typescript-expert) → "fix bug and commit"
|
|
113
|
-
|
|
114
|
-
✓ CORRECT: Separate file editing from git operations
|
|
115
|
-
Main conversation → Agent(lang-typescript-expert) → "fix bug" (file edit only)
|
|
116
|
-
Main conversation → Agent(mgr-gitnerd) → "git commit" (git operation only)
|
|
117
|
-
|
|
118
|
-
❌ WRONG: Including git commands in non-gitnerd agent prompt for "convenience"
|
|
119
107
|
Agent(general-purpose, prompt="revert the last commit, edit the file, then commit the fix")
|
|
120
108
|
|
|
121
|
-
✓ CORRECT:
|
|
109
|
+
✓ CORRECT: Separate file editing from git operations, split delegations
|
|
122
110
|
Agent(mgr-gitnerd, prompt="revert the last commit")
|
|
123
111
|
Agent(appropriate-expert, prompt="edit the file to fix the issue")
|
|
124
112
|
Agent(mgr-gitnerd, prompt="commit the fix")
|
|
@@ -16,26 +16,7 @@ Apply Django patterns for building production-ready, secure, and maintainable Py
|
|
|
16
16
|
```yaml
|
|
17
17
|
structure:
|
|
18
18
|
settings_split: true
|
|
19
|
-
layout:
|
|
20
|
-
project/
|
|
21
|
-
├── config/
|
|
22
|
-
│ ├── settings/
|
|
23
|
-
│ │ ├── base.py
|
|
24
|
-
│ │ ├── development.py
|
|
25
|
-
│ │ └── production.py
|
|
26
|
-
│ ├── urls.py
|
|
27
|
-
│ └── wsgi.py
|
|
28
|
-
├── apps/
|
|
29
|
-
│ ├── core/ # Shared utilities, base models
|
|
30
|
-
│ ├── users/ # Custom User model (ALWAYS create)
|
|
31
|
-
│ └── {feature}/ # Feature-specific apps
|
|
32
|
-
├── templates/
|
|
33
|
-
├── static/
|
|
34
|
-
├── requirements/
|
|
35
|
-
│ ├── base.txt
|
|
36
|
-
│ ├── development.txt
|
|
37
|
-
│ └── production.txt
|
|
38
|
-
└── manage.py
|
|
19
|
+
layout: "config/{settings/{base,development,production}.py,urls.py,wsgi.py} + apps/{core/,users/,<feature>/} + templates/ + static/ + requirements/{base,development,production}.txt"
|
|
39
20
|
|
|
40
21
|
app_module_contents:
|
|
41
22
|
models.py: Database models
|
|
@@ -50,6 +31,8 @@ app_module_contents:
|
|
|
50
31
|
tests/: Test suite (mirror app structure)
|
|
51
32
|
```
|
|
52
33
|
|
|
34
|
+
Reference: guides/django-best-practices/README.md
|
|
35
|
+
|
|
53
36
|
### 2. Models Best Practices
|
|
54
37
|
|
|
55
38
|
```yaml
|
|
@@ -57,15 +40,7 @@ custom_user_model:
|
|
|
57
40
|
rule: ALWAYS create a custom User model, even if identical to default
|
|
58
41
|
location: apps/users/models.py
|
|
59
42
|
reason: Impossible to swap default User model mid-project
|
|
60
|
-
|
|
61
|
-
# apps/users/models.py
|
|
62
|
-
from django.contrib.auth.models import AbstractUser
|
|
63
|
-
|
|
64
|
-
class User(AbstractUser):
|
|
65
|
-
pass # Can extend later without migrations
|
|
66
|
-
|
|
67
|
-
# config/settings/base.py
|
|
68
|
-
AUTH_USER_MODEL = 'users.User'
|
|
43
|
+
pattern: "Extend AbstractUser, set AUTH_USER_MODEL in settings"
|
|
69
44
|
|
|
70
45
|
primary_key:
|
|
71
46
|
default: BigAutoField
|
|
@@ -77,18 +52,6 @@ model_meta:
|
|
|
77
52
|
- Meta.ordering: consistent default ordering
|
|
78
53
|
- Meta.verbose_name: singular display name
|
|
79
54
|
- Meta.verbose_name_plural: plural display name
|
|
80
|
-
example: |
|
|
81
|
-
class Article(models.Model):
|
|
82
|
-
title = models.CharField(max_length=200)
|
|
83
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
|
84
|
-
|
|
85
|
-
class Meta:
|
|
86
|
-
ordering = ['-created_at']
|
|
87
|
-
verbose_name = 'article'
|
|
88
|
-
verbose_name_plural = 'articles'
|
|
89
|
-
|
|
90
|
-
def __str__(self):
|
|
91
|
-
return self.title
|
|
92
55
|
|
|
93
56
|
query_optimization:
|
|
94
57
|
foreign_key: select_related() # Single SQL JOIN
|
|
@@ -107,18 +70,7 @@ indexing:
|
|
|
107
70
|
|
|
108
71
|
constraints:
|
|
109
72
|
use: Meta.constraints for database-level enforcement
|
|
110
|
-
|
|
111
|
-
class Meta:
|
|
112
|
-
constraints = [
|
|
113
|
-
models.UniqueConstraint(
|
|
114
|
-
fields=['user', 'article'],
|
|
115
|
-
name='unique_user_article'
|
|
116
|
-
),
|
|
117
|
-
models.CheckConstraint(
|
|
118
|
-
check=models.Q(price__gte=0),
|
|
119
|
-
name='price_non_negative'
|
|
120
|
-
)
|
|
121
|
-
]
|
|
73
|
+
types: "UniqueConstraint, CheckConstraint"
|
|
122
74
|
|
|
123
75
|
soft_delete:
|
|
124
76
|
pattern: is_active = models.BooleanField(default=True)
|
|
@@ -126,16 +78,10 @@ soft_delete:
|
|
|
126
78
|
|
|
127
79
|
custom_managers:
|
|
128
80
|
rule: Use managers for reusable querysets
|
|
129
|
-
example: |
|
|
130
|
-
class PublishedManager(models.Manager):
|
|
131
|
-
def get_queryset(self):
|
|
132
|
-
return super().get_queryset().filter(status='published')
|
|
133
|
-
|
|
134
|
-
class Article(models.Model):
|
|
135
|
-
objects = models.Manager() # Keep default
|
|
136
|
-
published = PublishedManager() # Add custom
|
|
137
81
|
```
|
|
138
82
|
|
|
83
|
+
Reference: guides/django-best-practices/README.md
|
|
84
|
+
|
|
139
85
|
### 3. Views Best Practices
|
|
140
86
|
|
|
141
87
|
```yaml
|
|
@@ -145,15 +91,6 @@ cbv_vs_fbv:
|
|
|
145
91
|
|
|
146
92
|
thin_views:
|
|
147
93
|
rule: Keep views thin — delegate business logic to services/models
|
|
148
|
-
wrong: |
|
|
149
|
-
def create_order(request):
|
|
150
|
-
# 50 lines of business logic in view
|
|
151
|
-
correct: |
|
|
152
|
-
def create_order(request):
|
|
153
|
-
form = OrderForm(request.POST)
|
|
154
|
-
if form.is_valid():
|
|
155
|
-
order = order_service.create(request.user, form.cleaned_data)
|
|
156
|
-
return redirect('order-detail', pk=order.pk)
|
|
157
94
|
|
|
158
95
|
shortcuts:
|
|
159
96
|
- get_object_or_404(Model, pk=pk): Returns 404 instead of 500
|
|
@@ -179,7 +116,7 @@ status_codes:
|
|
|
179
116
|
```yaml
|
|
180
117
|
namespacing:
|
|
181
118
|
app_name: Required in every app's urls.py
|
|
182
|
-
usage: reverse('app_name:url_name') or {% url 'app_name:url_name' %}
|
|
119
|
+
usage: "reverse('app_name:url_name') or {% url 'app_name:url_name' %}"
|
|
183
120
|
|
|
184
121
|
syntax:
|
|
185
122
|
prefer: path() over re_path() for clarity
|
|
@@ -187,56 +124,29 @@ syntax:
|
|
|
187
124
|
|
|
188
125
|
naming:
|
|
189
126
|
rule: Name ALL URL patterns
|
|
190
|
-
convention: "{resource}-{action}
|
|
127
|
+
convention: "{resource}-{action} (e.g., article-list, article-detail)"
|
|
191
128
|
|
|
192
129
|
inclusion:
|
|
193
130
|
root_urls: Use include() for app-level URLs
|
|
194
|
-
example: |
|
|
195
|
-
# config/urls.py
|
|
196
|
-
urlpatterns = [
|
|
197
|
-
path('admin/', admin.site.urls),
|
|
198
|
-
path('api/', include('apps.api.urls', namespace='api')),
|
|
199
|
-
path('articles/', include('apps.articles.urls', namespace='articles')),
|
|
200
|
-
]
|
|
201
|
-
|
|
202
|
-
# apps/articles/urls.py
|
|
203
|
-
app_name = 'articles'
|
|
204
|
-
urlpatterns = [
|
|
205
|
-
path('', ArticleListView.as_view(), name='list'),
|
|
206
|
-
path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),
|
|
207
|
-
]
|
|
208
131
|
```
|
|
209
132
|
|
|
133
|
+
Reference: guides/django-best-practices/README.md
|
|
134
|
+
|
|
210
135
|
### 5. Forms & Validation
|
|
211
136
|
|
|
212
137
|
```yaml
|
|
213
138
|
model_forms:
|
|
214
139
|
rule: Use ModelForm when form maps to a model
|
|
215
|
-
fields: Explicitly list fields (never use fields = '__all__')
|
|
140
|
+
fields: "Explicitly list fields (never use fields = '__all__')"
|
|
216
141
|
|
|
217
142
|
validation:
|
|
218
143
|
field_level: clean_<field>() method
|
|
219
144
|
cross_field: clean() method
|
|
220
145
|
built_in: Use Django validators (MaxValueValidator, RegexValidator, etc.)
|
|
221
|
-
|
|
222
|
-
example: |
|
|
223
|
-
class ArticleForm(forms.ModelForm):
|
|
224
|
-
class Meta:
|
|
225
|
-
model = Article
|
|
226
|
-
fields = ['title', 'body', 'status']
|
|
227
|
-
|
|
228
|
-
def clean_title(self):
|
|
229
|
-
title = self.cleaned_data['title']
|
|
230
|
-
if len(title) < 5:
|
|
231
|
-
raise forms.ValidationError('Title too short.')
|
|
232
|
-
return title
|
|
233
|
-
|
|
234
|
-
def clean(self):
|
|
235
|
-
cleaned = super().clean()
|
|
236
|
-
# Cross-field validation here
|
|
237
|
-
return cleaned
|
|
238
146
|
```
|
|
239
147
|
|
|
148
|
+
Reference: guides/django-best-practices/README.md
|
|
149
|
+
|
|
240
150
|
### 6. Security
|
|
241
151
|
|
|
242
152
|
```yaml
|
|
@@ -287,22 +197,13 @@ test_classes:
|
|
|
287
197
|
test_data:
|
|
288
198
|
preferred: factory_boy or model_bakery
|
|
289
199
|
avoid: fixtures (hard to maintain, slow)
|
|
290
|
-
example: |
|
|
291
|
-
import factory
|
|
292
|
-
from apps.users.models import User
|
|
293
|
-
|
|
294
|
-
class UserFactory(factory.django.DjangoModelFactory):
|
|
295
|
-
class Meta:
|
|
296
|
-
model = User
|
|
297
|
-
username = factory.Sequence(lambda n: f'user{n}')
|
|
298
|
-
email = factory.LazyAttribute(lambda o: f'{o.username}@example.com')
|
|
299
200
|
|
|
300
201
|
request_testing:
|
|
301
202
|
Client: Full request/response cycle (preferred for views)
|
|
302
203
|
RequestFactory: Faster, no middleware (for unit testing views)
|
|
303
204
|
|
|
304
205
|
settings_override:
|
|
305
|
-
decorator: '@override_settings(
|
|
206
|
+
decorator: '@override_settings(...)'
|
|
306
207
|
|
|
307
208
|
coverage:
|
|
308
209
|
target: 80%+
|
|
@@ -312,6 +213,8 @@ structure:
|
|
|
312
213
|
mirror_app: tests/test_models.py, tests/test_views.py, tests/test_forms.py
|
|
313
214
|
```
|
|
314
215
|
|
|
216
|
+
Reference: guides/django-best-practices/README.md
|
|
217
|
+
|
|
315
218
|
### 8. Performance
|
|
316
219
|
|
|
317
220
|
```yaml
|
|
@@ -322,15 +225,15 @@ n_plus_1_prevention:
|
|
|
322
225
|
complex: Prefetch object with custom queryset
|
|
323
226
|
|
|
324
227
|
partial_loading:
|
|
325
|
-
only: only('id', 'title', 'created_at')
|
|
326
|
-
defer: defer('body', 'metadata')
|
|
327
|
-
values: values('id', 'title')
|
|
328
|
-
values_list: values_list('id', flat=True)
|
|
228
|
+
only: "only('id', 'title', 'created_at') — Load only these fields"
|
|
229
|
+
defer: "defer('body', 'metadata') — Load all except these"
|
|
230
|
+
values: "values('id', 'title') — Returns dicts (no ORM overhead)"
|
|
231
|
+
values_list: "values_list('id', flat=True) — Returns flat list"
|
|
329
232
|
|
|
330
233
|
caching:
|
|
331
234
|
backend: Redis (preferred), Memcached
|
|
332
|
-
view_cache: '@cache_page(60 * 15)' decorator
|
|
333
|
-
template_cache: '{% cache 500 sidebar %}' template tag
|
|
235
|
+
view_cache: "'@cache_page(60 * 15)' decorator"
|
|
236
|
+
template_cache: "'{% cache 500 sidebar %}' template tag"
|
|
334
237
|
low_level: cache.get/set/delete for fine-grained control
|
|
335
238
|
|
|
336
239
|
pagination:
|
|
@@ -339,8 +242,8 @@ pagination:
|
|
|
339
242
|
drf: PageNumberPagination or CursorPagination
|
|
340
243
|
|
|
341
244
|
bulk_operations:
|
|
342
|
-
create:
|
|
343
|
-
update:
|
|
245
|
+
create: "bulk_create(articles, batch_size=1000)"
|
|
246
|
+
update: "bulk_update(articles, ['status'], batch_size=1000)"
|
|
344
247
|
avoid: Loops calling .save() on many objects
|
|
345
248
|
```
|
|
346
249
|
|
|
@@ -351,17 +254,6 @@ serializers:
|
|
|
351
254
|
standard_crud: ModelSerializer
|
|
352
255
|
read_only: Use SerializerMethodField for computed values
|
|
353
256
|
write_validation: validate_<field>() and validate() methods
|
|
354
|
-
example: |
|
|
355
|
-
class ArticleSerializer(serializers.ModelSerializer):
|
|
356
|
-
author_name = serializers.SerializerMethodField()
|
|
357
|
-
|
|
358
|
-
class Meta:
|
|
359
|
-
model = Article
|
|
360
|
-
fields = ['id', 'title', 'body', 'author_name', 'created_at']
|
|
361
|
-
read_only_fields = ['id', 'created_at']
|
|
362
|
-
|
|
363
|
-
def get_author_name(self, obj):
|
|
364
|
-
return obj.author.get_full_name()
|
|
365
257
|
|
|
366
258
|
viewsets:
|
|
367
259
|
standard: ModelViewSet for full CRUD
|
|
@@ -380,7 +272,6 @@ permissions:
|
|
|
380
272
|
|
|
381
273
|
versioning:
|
|
382
274
|
method: NamespaceVersioning or URLPathVersioning
|
|
383
|
-
example: "/api/v1/articles/" vs "/api/v2/articles/"
|
|
384
275
|
|
|
385
276
|
throttling:
|
|
386
277
|
anonymous: AnonRateThrottle
|
|
@@ -392,6 +283,8 @@ pagination:
|
|
|
392
283
|
types: PageNumberPagination (simple), CursorPagination (large datasets)
|
|
393
284
|
```
|
|
394
285
|
|
|
286
|
+
Reference: guides/django-best-practices/README.md
|
|
287
|
+
|
|
395
288
|
### 10. Deployment
|
|
396
289
|
|
|
397
290
|
```yaml
|