cc-codeconductor 0.2.1
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/LICENSE +18 -0
- package/README.md +319 -0
- package/dist/index.js +12741 -0
- package/package.json +51 -0
- package/policy.yml +124 -0
- package/presets/claude/CLAUDE.md +598 -0
- package/presets/claude/commands/cc/feature.md +115 -0
- package/presets/claude/commands/cc/fix.md +121 -0
- package/presets/claude/commands/cc/refactor.md +148 -0
- package/presets/claude/commands/cc/review.md +126 -0
- package/presets/claude/commands/cc/tdd-cycle.md +226 -0
- package/presets/claude/commands/cc/test-plan.md +138 -0
- package/presets/claude/settings.json +37 -0
- package/presets/claude/skills/api-versioning/SKILL.md +389 -0
- package/presets/claude/skills/django-orm/SKILL.md +455 -0
- package/presets/claude/skills/django-testing/SKILL.md +409 -0
- package/presets/claude/skills/jpa-postgres/SKILL.md +618 -0
- package/presets/claude/skills/python/SKILL.md +606 -0
- package/presets/claude/skills/python-django-stack/SKILL.md +492 -0
- package/presets/claude/skills/python-fastapi-stack/SKILL.md +457 -0
- package/presets/claude/skills/spring-boot-feature/SKILL.md +558 -0
- package/presets/claude/skills/spring-boot-kotlin/SKILL.md +400 -0
- package/presets/claude/skills/sqlalchemy/SKILL.md +466 -0
- package/presets/claude/skills/testing-strategy/SKILL.md +479 -0
- package/presets/codex/AGENTS.md +883 -0
- package/presets/codex/README.md +102 -0
- package/presets/codex/skills/api-versioning/SKILL.md +389 -0
- package/presets/codex/skills/django-orm/SKILL.md +455 -0
- package/presets/codex/skills/django-testing/SKILL.md +409 -0
- package/presets/codex/skills/jpa-postgres/SKILL.md +618 -0
- package/presets/codex/skills/python/SKILL.md +606 -0
- package/presets/codex/skills/python-django-stack/SKILL.md +492 -0
- package/presets/codex/skills/python-fastapi-stack/SKILL.md +457 -0
- package/presets/codex/skills/spring-boot-feature/SKILL.md +558 -0
- package/presets/codex/skills/spring-boot-kotlin/SKILL.md +400 -0
- package/presets/codex/skills/sqlalchemy/SKILL.md +466 -0
- package/presets/codex/skills/testing-strategy/SKILL.md +479 -0
- package/presets/opencode/README.md +191 -0
- package/presets/opencode/agents/architect.md +133 -0
- package/presets/opencode/agents/docs.md +113 -0
- package/presets/opencode/agents/implementer.md +116 -0
- package/presets/opencode/agents/orchestrator.md +248 -0
- package/presets/opencode/agents/repo-explorer.md +119 -0
- package/presets/opencode/agents/reviewer.md +130 -0
- package/presets/opencode/agents/task-coach.md +103 -0
- package/presets/opencode/agents/tester.md +269 -0
- package/presets/opencode/commands/cc-feature.md +114 -0
- package/presets/opencode/commands/cc-fix.md +123 -0
- package/presets/opencode/commands/cc-refactor.md +148 -0
- package/presets/opencode/commands/cc-review.md +141 -0
- package/presets/opencode/commands/cc-tdd-cycle.md +225 -0
- package/presets/opencode/commands/cc-test-plan.md +144 -0
- package/presets/opencode/opencode.jsonc +89 -0
- package/presets/opencode/prompts/v0.1.0/architect.md +213 -0
- package/presets/opencode/prompts/v0.1.0/docs.md +181 -0
- package/presets/opencode/prompts/v0.1.0/implementer.md +154 -0
- package/presets/opencode/prompts/v0.1.0/orchestrator.md +169 -0
- package/presets/opencode/prompts/v0.1.0/repo-explorer.md +102 -0
- package/presets/opencode/prompts/v0.1.0/reviewer.md +183 -0
- package/presets/opencode/prompts/v0.1.0/task-coach.md +142 -0
- package/presets/opencode/prompts/v0.1.0/tester.md +160 -0
- package/presets/opencode/prompts/v0.2.0/architect.md +219 -0
- package/presets/opencode/prompts/v0.2.0/docs.md +187 -0
- package/presets/opencode/prompts/v0.2.0/implementer.md +160 -0
- package/presets/opencode/prompts/v0.2.0/orchestrator.md +238 -0
- package/presets/opencode/prompts/v0.2.0/repo-explorer.md +108 -0
- package/presets/opencode/prompts/v0.2.0/reviewer.md +190 -0
- package/presets/opencode/prompts/v0.2.0/task-coach.md +153 -0
- package/presets/opencode/prompts/v0.2.0/tester.md +249 -0
- package/presets/opencode/skills/api-versioning/SKILL.md +388 -0
- package/presets/opencode/skills/astro/SKILL.md +313 -0
- package/presets/opencode/skills/code-review/SKILL.md +202 -0
- package/presets/opencode/skills/django-orm/SKILL.md +455 -0
- package/presets/opencode/skills/django-testing/SKILL.md +408 -0
- package/presets/opencode/skills/django-uv/SKILL.md +400 -0
- package/presets/opencode/skills/jpa-postgres/SKILL.md +617 -0
- package/presets/opencode/skills/nextjs-typescript/SKILL.md +385 -0
- package/presets/opencode/skills/python/SKILL.md +605 -0
- package/presets/opencode/skills/python-django-stack/SKILL.md +491 -0
- package/presets/opencode/skills/python-fastapi-stack/SKILL.md +456 -0
- package/presets/opencode/skills/security/SKILL.md +376 -0
- package/presets/opencode/skills/spring-boot-feature/SKILL.md +557 -0
- package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +399 -0
- package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +470 -0
- package/presets/opencode/skills/sqlalchemy/SKILL.md +465 -0
- package/presets/opencode/skills/testing-tdd/SKILL.md +586 -0
- package/src/presets/council/council.yml +59 -0
- package/src/presets/manifests/claude.yml +23 -0
- package/src/presets/manifests/codex.yml +12 -0
- package/src/presets/manifests/opencode.yml +18 -0
- package/src/presets/models/claude.yml +37 -0
- package/src/presets/models/codex.yml +38 -0
- package/src/presets/models/opencode.yml +37 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: code-review
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
name: Code Review
|
|
5
|
+
description: >
|
|
6
|
+
Provides a structured framework for conducting thorough code reviews covering correctness, security, architecture alignment, performance, and technical debt.
|
|
7
|
+
|
|
8
|
+
compatibility:
|
|
9
|
+
tools: [claude, codex, opencode]
|
|
10
|
+
stacks:
|
|
11
|
+
languages: []
|
|
12
|
+
frameworks: []
|
|
13
|
+
|
|
14
|
+
risk:
|
|
15
|
+
level: low
|
|
16
|
+
can_execute_shell: false
|
|
17
|
+
can_modify_files: true
|
|
18
|
+
requires_network: false
|
|
19
|
+
|
|
20
|
+
inputs: []
|
|
21
|
+
|
|
22
|
+
outputs: []
|
|
23
|
+
|
|
24
|
+
quality:
|
|
25
|
+
reviewed_by: codeconductor-core
|
|
26
|
+
version: 0.1.0
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Code Review
|
|
32
|
+
|
|
33
|
+
## Review Axes
|
|
34
|
+
|
|
35
|
+
Every finding must reference exactly one axis. This prevents vague feedback and
|
|
36
|
+
makes it actionable.
|
|
37
|
+
|
|
38
|
+
| Axis | What to check |
|
|
39
|
+
|------|--------------|
|
|
40
|
+
| **Correctness** | Does the logic handle all cases in the acceptance criteria? |
|
|
41
|
+
| **Security** | Injection vectors, secret exposure, auth bypasses, insecure defaults |
|
|
42
|
+
| **Architecture** | Does the code follow existing patterns and module boundaries? |
|
|
43
|
+
| **Performance** | N+1 queries, unnecessary allocations, blocking I/O in hot paths |
|
|
44
|
+
| **Error handling** | Are failure cases handled explicitly and safely? |
|
|
45
|
+
| **Test coverage** | Do tests verify the acceptance criteria, not just happy paths? |
|
|
46
|
+
| **Scope** | Are there changes outside the stated task boundary? |
|
|
47
|
+
| **Technical debt** | Does the implementation introduce debt without acknowledging it? |
|
|
48
|
+
|
|
49
|
+
## Finding Categories
|
|
50
|
+
|
|
51
|
+
### CRITICAL — must be resolved before merge
|
|
52
|
+
|
|
53
|
+
- Logic failures that violate acceptance criteria
|
|
54
|
+
- Security vulnerabilities (injection, auth bypass, secret exposure)
|
|
55
|
+
- Data loss or corruption risk
|
|
56
|
+
- Breaking changes to public API or shared contracts
|
|
57
|
+
- Missing error handling that causes silent failures
|
|
58
|
+
|
|
59
|
+
### WARNING — should be resolved before merge
|
|
60
|
+
|
|
61
|
+
- Missing edge case coverage in tests
|
|
62
|
+
- Scope creep (changes outside the task boundary)
|
|
63
|
+
- Pattern inconsistency that will confuse future maintainers
|
|
64
|
+
- Performance issue that will degrade under load
|
|
65
|
+
- Missing input validation at system boundaries
|
|
66
|
+
|
|
67
|
+
### SUGGESTION — optional improvement
|
|
68
|
+
|
|
69
|
+
- Naming clarity improvements
|
|
70
|
+
- Extracting a reusable abstraction
|
|
71
|
+
- Test readability
|
|
72
|
+
- Documentation gaps on non-obvious behavior
|
|
73
|
+
|
|
74
|
+
## Review Report Format
|
|
75
|
+
|
|
76
|
+
```markdown
|
|
77
|
+
## Review Report
|
|
78
|
+
|
|
79
|
+
**Task**: [objective from task card]
|
|
80
|
+
**Verdict**: [approved | approved with warnings | blocked]
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### CRITICAL
|
|
85
|
+
|
|
86
|
+
- [ ] [C1] `path/to/file:line` — [description]
|
|
87
|
+
Axis: [axis] | Evidence: `[quoted code]` | Required action: [what must change]
|
|
88
|
+
|
|
89
|
+
_(none)_ if no critical findings
|
|
90
|
+
|
|
91
|
+
### WARNING
|
|
92
|
+
|
|
93
|
+
- [ ] [W1] `path/to/file:line` — [description]
|
|
94
|
+
Axis: [axis] | Recommended action: [what should change]
|
|
95
|
+
|
|
96
|
+
_(none)_ if no warning findings
|
|
97
|
+
|
|
98
|
+
### SUGGESTION
|
|
99
|
+
|
|
100
|
+
- [ ] [S1] — [description] | Rationale: [brief reason]
|
|
101
|
+
|
|
102
|
+
_(none)_ if no suggestions
|
|
103
|
+
|
|
104
|
+
### Summary
|
|
105
|
+
|
|
106
|
+
- Critical: [count] | Warning: [count] | Suggestion: [count]
|
|
107
|
+
- **Verdict justification**: [one sentence]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Verdict rules:**
|
|
111
|
+
|
|
112
|
+
- `blocked` — any CRITICAL finding present
|
|
113
|
+
- `approved with warnings` — no CRITICAL, at least one WARNING
|
|
114
|
+
- `approved` — no CRITICAL, no WARNING
|
|
115
|
+
|
|
116
|
+
## What to Check — by Axis
|
|
117
|
+
|
|
118
|
+
### Correctness
|
|
119
|
+
|
|
120
|
+
- Does every acceptance criterion have a corresponding code path?
|
|
121
|
+
- Are null/empty/zero values handled explicitly?
|
|
122
|
+
- Are boundary conditions checked (off-by-one, empty collections, max values)?
|
|
123
|
+
- Do conditional branches cover all cases (exhaustive when/switch)?
|
|
124
|
+
|
|
125
|
+
### Security
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
Injection: SQL, command, LDAP, XML — is input sanitized or parameterized?
|
|
129
|
+
Auth: is the endpoint protected? are role checks correct?
|
|
130
|
+
Secrets: no hardcoded credentials, tokens, or keys in source
|
|
131
|
+
Headers: are security headers set (CSP, HSTS, X-Frame-Options)?
|
|
132
|
+
CORS: is the origin whitelist explicit, not wildcard?
|
|
133
|
+
Dependencies: are new dependencies from trusted sources?
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Performance
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
N+1 queries: does a loop call the database per iteration?
|
|
140
|
+
Eager loading: are JOINs or includes used where needed?
|
|
141
|
+
Pagination: are all list endpoints paginated?
|
|
142
|
+
Caching: is expensive computation cached at an appropriate layer?
|
|
143
|
+
Index: do new query filters have corresponding DB indexes?
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Error Handling
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
Are exceptions caught at the right layer (not swallowed silently)?
|
|
150
|
+
Is the error response format consistent with the rest of the API?
|
|
151
|
+
Is the original exception logged before translating to a user-facing error?
|
|
152
|
+
Are transient failures retried with backoff, or propagated immediately?
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## What NOT to Flag
|
|
156
|
+
|
|
157
|
+
**Personal style preferences.** If the code follows the project's established
|
|
158
|
+
conventions, do not flag it because you would have written it differently.
|
|
159
|
+
|
|
160
|
+
**Trivial naming.** Variable names are a SUGGESTION at most, never a WARNING.
|
|
161
|
+
Do not block a PR over naming unless the name is genuinely misleading.
|
|
162
|
+
|
|
163
|
+
**Tests for trivial code.** Do not demand tests for getters, setters, or data
|
|
164
|
+
class construction. Test behavior, not boilerplate.
|
|
165
|
+
|
|
166
|
+
**Refactors not in scope.** If you notice an improvement opportunity outside
|
|
167
|
+
the task boundary, log it as a SUGGESTION. Do not block the PR for work that
|
|
168
|
+
was not requested.
|
|
169
|
+
|
|
170
|
+
**Framework defaults.** Do not second-guess framework conventions (Spring
|
|
171
|
+
dependency injection, Next.js file-based routing). Trust the framework.
|
|
172
|
+
|
|
173
|
+
## Exact Diff Recommendations
|
|
174
|
+
|
|
175
|
+
When a finding requires a code change, provide the exact replacement:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
# Current code (path/to/file:42)
|
|
179
|
+
val user = userRepository.findById(id)
|
|
180
|
+
return user.name // NPE if not found
|
|
181
|
+
|
|
182
|
+
# Recommended fix
|
|
183
|
+
val user = userRepository.findById(id)
|
|
184
|
+
?: throw NotFoundException("User $id not found")
|
|
185
|
+
return user.name
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Vague instructions ("handle the null case") force the author to guess your
|
|
189
|
+
intent. Exact diffs remove ambiguity and speed up the review cycle.
|
|
190
|
+
|
|
191
|
+
## Prioritization Order
|
|
192
|
+
|
|
193
|
+
When multiple findings exist, address them in this order:
|
|
194
|
+
|
|
195
|
+
1. Security — security issues can silently compromise users or data
|
|
196
|
+
2. Correctness — broken behavior defeats the purpose of the change
|
|
197
|
+
3. Error handling — silent failures are hard to debug in production
|
|
198
|
+
4. Test coverage — gaps mean regressions will be invisible
|
|
199
|
+
5. Performance — degrade gradually; fix before it causes incidents
|
|
200
|
+
6. Architecture — inconsistency compounds over time
|
|
201
|
+
7. Technical debt — acknowledge and track; do not always block for it
|
|
202
|
+
8. Suggestions — apply at author's discretion
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: django-orm
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
name: Django ORM
|
|
5
|
+
description: >
|
|
6
|
+
Django ORM patterns for multi-tenant POS projects: efficient queries,
|
|
7
|
+
bulk operations, transactions, and multi-schema upload paths.
|
|
8
|
+
Trigger: When writing queryset logic, model saves, or DB-touching service code.
|
|
9
|
+
|
|
10
|
+
compatibility:
|
|
11
|
+
tools: [claude, codex, opencode]
|
|
12
|
+
stacks:
|
|
13
|
+
languages: [python]
|
|
14
|
+
frameworks: [django, django-tenants]
|
|
15
|
+
|
|
16
|
+
risk:
|
|
17
|
+
level: medium
|
|
18
|
+
can_execute_shell: true
|
|
19
|
+
can_modify_files: true
|
|
20
|
+
requires_network: false
|
|
21
|
+
|
|
22
|
+
inputs:
|
|
23
|
+
- model files (models.py)
|
|
24
|
+
- service files (services.py)
|
|
25
|
+
- view files with queryset logic
|
|
26
|
+
- existing queryset or bulk-operation code
|
|
27
|
+
|
|
28
|
+
outputs:
|
|
29
|
+
- optimized queryset patterns with select_related / prefetch_related
|
|
30
|
+
- bulk_create and bulk_update implementations
|
|
31
|
+
- transaction-safe service methods
|
|
32
|
+
- annotated and aggregated querysets
|
|
33
|
+
- migration commands for multi-schema projects
|
|
34
|
+
|
|
35
|
+
quality:
|
|
36
|
+
reviewed_by: codeconductor-core
|
|
37
|
+
version: 0.1.0
|
|
38
|
+
---
|
|
39
|
+
## When to Use
|
|
40
|
+
|
|
41
|
+
- Writing or reviewing queryset code in any `apps/*/`
|
|
42
|
+
- Adding aggregations, filters, or annotations to a queryset
|
|
43
|
+
- Writing model `save()` overrides or signals
|
|
44
|
+
- Implementing service-layer DB operations
|
|
45
|
+
- Optimizing queries that traverse FK relationships
|
|
46
|
+
|
|
47
|
+
## Performance Fundamentals
|
|
48
|
+
|
|
49
|
+
### The N+1 Problem
|
|
50
|
+
|
|
51
|
+
The most common Django mistake: iterating over a queryset and accessing FK
|
|
52
|
+
fields without `select_related`. Each access generates a separate DB query.
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
# Initial query: 1 query
|
|
56
|
+
products = Product.objects.all()
|
|
57
|
+
|
|
58
|
+
# In the loop: N additional queries (one per product)
|
|
59
|
+
for p in products:
|
|
60
|
+
print(p.category.name) # category_id → SELECT * FROM category WHERE id = ?
|
|
61
|
+
|
|
62
|
+
# Total: 1 + N queries
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Solution: select_related and prefetch_related
|
|
66
|
+
|
|
67
|
+
| Method | Use | SQL Query |
|
|
68
|
+
| ---------------------------------- | -------------------------- | ------------------------ |
|
|
69
|
+
| `select_related` | FK one-to-one or ManyToOne | Automatic JOIN |
|
|
70
|
+
| `prefetch_related` | Reverse FK or ManyToMany | 2 separate queries |
|
|
71
|
+
| `prefetch_related` with `Prefetch` | Custom queryset | Filtered in nested query |
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
# FK traversal — use select_related
|
|
75
|
+
products = Product.objects.select_related("category", "tax")
|
|
76
|
+
|
|
77
|
+
# Multiple FK
|
|
78
|
+
products = Product.objects.select_related("category", "tax", "supplier")
|
|
79
|
+
|
|
80
|
+
# Reverse FK (one-to-many) — use prefetch_related
|
|
81
|
+
categories = Category.objects.prefetch_related("products")
|
|
82
|
+
|
|
83
|
+
# ManyToMany — prefetch_related
|
|
84
|
+
product = Product.objects.prefetch_related("tags").first()
|
|
85
|
+
|
|
86
|
+
# Custom prefetch with filter
|
|
87
|
+
from django.db.models import Prefetch
|
|
88
|
+
|
|
89
|
+
products = Product.objects.prefetch_related(
|
|
90
|
+
Prefetch(
|
|
91
|
+
"order_items",
|
|
92
|
+
queryset=OrderItem.objects.filter(order__status="paid")
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Query Patterns
|
|
98
|
+
|
|
99
|
+
### 1. Efficient Filtering
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# Basic filtering
|
|
103
|
+
Product.objects.filter(is_active=True, stock__gt=0)
|
|
104
|
+
|
|
105
|
+
# Filtering with Q objects — OR and negations
|
|
106
|
+
from django.db.models import Q
|
|
107
|
+
|
|
108
|
+
Product.objects.filter(
|
|
109
|
+
Q(is_active=True) & (Q(stock__gt=0) | Q(is_service=True))
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Exclusion
|
|
113
|
+
Product.objects.exclude(status="draft")
|
|
114
|
+
|
|
115
|
+
# Filter by nested FK
|
|
116
|
+
Order.objects.filter(employee__store=store)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Annotations and Aggregations
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from django.db.models import Count, Sum, Avg, Max, Min, F, Q, Case, When, Value, CharField
|
|
123
|
+
from django.db.models.functions import Coalesce, Concat
|
|
124
|
+
|
|
125
|
+
# Conditional sum with Coalesce (avoids None)
|
|
126
|
+
Product.objects.annotate(
|
|
127
|
+
total_sold=Coalesce(
|
|
128
|
+
Sum(
|
|
129
|
+
"order_items__quantity",
|
|
130
|
+
filter=Q(
|
|
131
|
+
order_items__order__status__in=["paid", "shipped"],
|
|
132
|
+
order_items__order__created_at__gte=cutoff,
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
0,
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Use F for arithmetic operations
|
|
140
|
+
Product.objects.update(stock=F("stock") - 1)
|
|
141
|
+
|
|
142
|
+
# Annotation with Case/When for conditional logic
|
|
143
|
+
Product.objects.annotate(
|
|
144
|
+
status_flag=Case(
|
|
145
|
+
When(stock__lte=0, then=Value("out_of_stock")),
|
|
146
|
+
When(stock__lte=10, then=Value("low_stock")),
|
|
147
|
+
default=Value("available"),
|
|
148
|
+
output_field=CharField(),
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Aggregation with filter
|
|
153
|
+
Store.objects.annotate(
|
|
154
|
+
paid_orders=Count("orders", filter=Q(orders__status="paid")),
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3. Exists vs Count
|
|
159
|
+
|
|
160
|
+
**Golden rule**: For existence checks, use `Exists`, never `Count`.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from django.db.models import Exists, OuterRef, Count
|
|
164
|
+
|
|
165
|
+
# WRONG — loads full count
|
|
166
|
+
products = Product.objects.annotate(
|
|
167
|
+
order_count=Count("order_items")
|
|
168
|
+
).filter(order_count__gt=0)
|
|
169
|
+
|
|
170
|
+
# CORRECT — short-circuits at first match
|
|
171
|
+
products = Product.objects.annotate(
|
|
172
|
+
has_orders=Exists(OrderItem.objects.filter(product=OuterRef("pk")))
|
|
173
|
+
).filter(has_orders=True)
|
|
174
|
+
|
|
175
|
+
# With more complex subquery
|
|
176
|
+
from django.db.models import Subquery
|
|
177
|
+
|
|
178
|
+
latest_order = Order.objects.filter(
|
|
179
|
+
customer=OuterRef("customer_id")
|
|
180
|
+
).order_by("-created_at")
|
|
181
|
+
|
|
182
|
+
Customer.objects.annotate(
|
|
183
|
+
last_order_date=Subquery(
|
|
184
|
+
latest_order.values("created_at")[:1]
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 4. Ordering
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# Basic ordering
|
|
193
|
+
Product.objects.order_by("name")
|
|
194
|
+
|
|
195
|
+
# Ordering with NullsFirst/NullsLast
|
|
196
|
+
Product.objects.order_by(F("price").nulls_last())
|
|
197
|
+
|
|
198
|
+
# Ordering by annotation
|
|
199
|
+
Store.objects.annotate(
|
|
200
|
+
order_count=Count("orders")
|
|
201
|
+
).order_by("-order_count")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Write Operations
|
|
205
|
+
|
|
206
|
+
### 5. Bulk Operations — Anti-N+1
|
|
207
|
+
|
|
208
|
+
**Never do `save()` inside a loop.** Build lists and use bulk operations:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
# Get with lock once
|
|
212
|
+
products = {p.id: p for p in Product.objects.select_for_update().filter(id__in=ids)}
|
|
213
|
+
|
|
214
|
+
order_items_to_create = []
|
|
215
|
+
products_to_update = []
|
|
216
|
+
|
|
217
|
+
for item in cart:
|
|
218
|
+
product = products[item["id"]]
|
|
219
|
+
product.stock -= item["quantity"]
|
|
220
|
+
order_items_to_create.append(
|
|
221
|
+
OrderItem(order=order, product=product, quantity=item["quantity"], ...)
|
|
222
|
+
)
|
|
223
|
+
products_to_update.append(product)
|
|
224
|
+
|
|
225
|
+
# Bulk create and update
|
|
226
|
+
OrderItem.objects.bulk_create(order_items_to_create)
|
|
227
|
+
Product.objects.bulk_update(products_to_update, ["stock", "updated_at"])
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Bulk operation table:**
|
|
231
|
+
|
|
232
|
+
| Method | Use case | Returns |
|
|
233
|
+
| ------------------------------------------- | -------------------------- | ----------------- |
|
|
234
|
+
| `bulk_create(items)` | Create multiple records | List of objects |
|
|
235
|
+
| `bulk_update(items, fields)` | Update multiple records | None (in-place) |
|
|
236
|
+
| `bulk_create(items, ignore_conflicts=True)` | Ignore duplicates | List (IDs only) |
|
|
237
|
+
| `update()` | Mass update without return | Count of affected |
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# bulk_create with ignore_conflicts
|
|
241
|
+
Product.objects.bulk_create(new_products, ignore_conflicts=True)
|
|
242
|
+
|
|
243
|
+
# bulk update
|
|
244
|
+
Product.objects.filter(category=cat).update(is_active=False)
|
|
245
|
+
|
|
246
|
+
# update with F expressions
|
|
247
|
+
Product.objects.update(stock=F("stock") - 1)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 6. Surgical Saves — update_fields
|
|
251
|
+
|
|
252
|
+
**Always pass `update_fields`** when saving a subset of fields:
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# Update single field
|
|
256
|
+
product.stock -= 1
|
|
257
|
+
product.save(update_fields=["stock"])
|
|
258
|
+
|
|
259
|
+
# Update multiple fields
|
|
260
|
+
order.status = "paid"
|
|
261
|
+
order.paid_at = timezone.now()
|
|
262
|
+
order.save(update_fields=["status", "paid_at", "updated_at"])
|
|
263
|
+
|
|
264
|
+
# Don't use in migrations — that uses reconstructor
|
|
265
|
+
# DO use in application code
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Why?** Prevents:
|
|
269
|
+
|
|
270
|
+
- Accidental image reprocessing
|
|
271
|
+
- Unnecessary signals
|
|
272
|
+
- Race conditions on unrelated fields
|
|
273
|
+
|
|
274
|
+
### 7. Transactions
|
|
275
|
+
|
|
276
|
+
Use `transaction.atomic()` as a context manager, never as a decorator:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
from django.db import transaction
|
|
280
|
+
|
|
281
|
+
# CORRECT — clear context, automatic error handling
|
|
282
|
+
with transaction.atomic():
|
|
283
|
+
order.save()
|
|
284
|
+
OrderItem.objects.bulk_create(items)
|
|
285
|
+
Product.objects.bulk_update(products, ["stock"])
|
|
286
|
+
cart.clear()
|
|
287
|
+
|
|
288
|
+
# With select_for_update inside the transaction
|
|
289
|
+
with transaction.atomic():
|
|
290
|
+
product = Product.objects.select_for_update().get(pk=product_id)
|
|
291
|
+
product.stock -= quantity
|
|
292
|
+
product.save(update_fields=["stock", "updated_at"])
|
|
293
|
+
|
|
294
|
+
# WRONG — decorator hides intent
|
|
295
|
+
@transaction.atomic
|
|
296
|
+
def create_order(...):
|
|
297
|
+
...
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Isolation levels:**
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
from django.db import transaction
|
|
304
|
+
|
|
305
|
+
# Serializable — maximum isolation
|
|
306
|
+
with transaction.atomic():
|
|
307
|
+
...
|
|
308
|
+
|
|
309
|
+
# Read committed (default) — may cause dirty reads on Edge
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Multi-Tenant and Upload Paths
|
|
313
|
+
|
|
314
|
+
### 8. Upload Paths with Schema
|
|
315
|
+
|
|
316
|
+
Every `FileField`/`ImageField` must include `connection.schema_name` to isolate
|
|
317
|
+
files per tenant:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
import os
|
|
321
|
+
from django.db import connection
|
|
322
|
+
|
|
323
|
+
def get_upload_path(instance, filename):
|
|
324
|
+
schema = connection.schema_name
|
|
325
|
+
ext = filename.split(".")[-1].lower()
|
|
326
|
+
safe_name = f"{uuid4().hex}.{ext}"
|
|
327
|
+
return os.path.join(schema, "products", safe_name)
|
|
328
|
+
|
|
329
|
+
def get_thumb_path(instance, filename):
|
|
330
|
+
schema = connection.schema_name
|
|
331
|
+
return os.path.join(schema, "products", "thumbs", filename)
|
|
332
|
+
|
|
333
|
+
class Product(models.Model):
|
|
334
|
+
image = models.ImageField(upload_to=get_upload_path, storage=S3Storage())
|
|
335
|
+
thumbnail = models.ImageField(upload_to=get_thumb_path, blank=True)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Advanced Queries
|
|
339
|
+
|
|
340
|
+
### 9. Subqueries
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
from django.db.models import Subquery, OuterRef
|
|
344
|
+
|
|
345
|
+
# Subquery to get last value
|
|
346
|
+
latest_price = ProductPrice.objects.filter(
|
|
347
|
+
product=OuterRef("pk")
|
|
348
|
+
).order_by("-valid_from").values("price")[:1]
|
|
349
|
+
|
|
350
|
+
Product.objects.annotate(current_price=Subquery(latest_price))
|
|
351
|
+
|
|
352
|
+
# Subquery with aggregate
|
|
353
|
+
order_total = OrderItem.objects.filter(
|
|
354
|
+
order=OuterRef("pk")
|
|
355
|
+
).values("order").annotate(total=Sum("subtotal")).values("total")
|
|
356
|
+
|
|
357
|
+
Order.objects.annotate(order_total=Subquery(order_total))
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 10. Raw Queries — WHEN TO USE
|
|
361
|
+
|
|
362
|
+
Avoid raw queries unless necessary. Use when:
|
|
363
|
+
|
|
364
|
+
- Complex aggregation functions not supported by ORM
|
|
365
|
+
- Queries with multiple JOINs manually optimized
|
|
366
|
+
- Very complex queries where ORM generates inefficient SQL
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
# With safe parameters (never string interpolation!)
|
|
370
|
+
Product.objects.raw(
|
|
371
|
+
"SELECT * FROM catalog_product WHERE tsvector @@ plainto_tsquery(%s)",
|
|
372
|
+
[search_term]
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# With cursor for complex cases
|
|
376
|
+
from django.db import connection
|
|
377
|
+
|
|
378
|
+
with connection.cursor() as cursor:
|
|
379
|
+
cursor.execute("SELECT ...", [params])
|
|
380
|
+
results = cursor.fetchall()
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Migration Commands
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# Always migrate both schemas
|
|
387
|
+
uv run python manage.py migrate_schemas --shared
|
|
388
|
+
uv run python manage.py migrate_schemas --tenant
|
|
389
|
+
|
|
390
|
+
# Check for pending migrations
|
|
391
|
+
make verifymigrations
|
|
392
|
+
|
|
393
|
+
# Create specific migration
|
|
394
|
+
uv run python manage.py makemigrations catalog --name add_thumbnail
|
|
395
|
+
|
|
396
|
+
# Shows SQL without running
|
|
397
|
+
uv run python manage.py migrate --fake catalog 0003
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Common Mistakes and How to Avoid Them
|
|
401
|
+
|
|
402
|
+
### N+1 in templates
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
# WRONG — N queries
|
|
406
|
+
{% for product in products %}
|
|
407
|
+
{{ product.category.name }}
|
|
408
|
+
{% endfor %}
|
|
409
|
+
|
|
410
|
+
# CORRECT — 1 query with select_related
|
|
411
|
+
products = Product.objects.select_related("category")
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### ForeignKey without related_name
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
# WRONG
|
|
418
|
+
class Order(models.Model):
|
|
419
|
+
customer = models.ForeignKey("users.User", on_delete=...)
|
|
420
|
+
|
|
421
|
+
# CORRECT — always add related_name
|
|
422
|
+
class Order(models.Model):
|
|
423
|
+
customer = models.ForeignKey(
|
|
424
|
+
"users.User",
|
|
425
|
+
on_delete=models.CASCADE,
|
|
426
|
+
related_name="orders",
|
|
427
|
+
)
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Queries in signals
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
# WRONG — signal makes additional query
|
|
434
|
+
@receiver(post_save, sender=Order)
|
|
435
|
+
def on_order_save(sender, instance, **kwargs):
|
|
436
|
+
customer = instance.customer # Additional query!
|
|
437
|
+
send_email(customer.email, ...)
|
|
438
|
+
|
|
439
|
+
# CORRECT — pass the object, not the ID
|
|
440
|
+
@receiver(post_save, sender=Order)
|
|
441
|
+
def on_order_save(sender, instance, created, **kwargs):
|
|
442
|
+
if created:
|
|
443
|
+
customer = instance.customer
|
|
444
|
+
send_email(customer.email, ...)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Resources
|
|
448
|
+
|
|
449
|
+
- **Bulk write pattern**: `apps/orders/services.py`, `apps/pos/views.py` lines
|
|
450
|
+
216-246
|
|
451
|
+
- **Annotation examples**: `apps/pos/views.py` lines 59-75
|
|
452
|
+
- **Exists usage**: `apps/catalog/views.py` line 112
|
|
453
|
+
- **Upload paths**: `apps/catalog/models.py`
|
|
454
|
+
- **Django ORM docs**:
|
|
455
|
+
<https://docs.djangoproject.com/en/5.2/topics/db/queries/>
|