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,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
|
+
|
|
40
|
+
## When to Use
|
|
41
|
+
|
|
42
|
+
- Writing or reviewing queryset code in any `apps/*/`
|
|
43
|
+
- Adding aggregations, filters, or annotations to a queryset
|
|
44
|
+
- Writing model `save()` overrides or signals
|
|
45
|
+
- Implementing service-layer DB operations
|
|
46
|
+
- Optimizing queries that traverse FK relationships
|
|
47
|
+
|
|
48
|
+
## Performance Fundamentals
|
|
49
|
+
|
|
50
|
+
### The N+1 Problem
|
|
51
|
+
|
|
52
|
+
The most common Django mistake: iterating over a queryset and accessing FK
|
|
53
|
+
fields without `select_related`. Each access generates a separate DB query.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
# Initial query: 1 query
|
|
57
|
+
products = Product.objects.all()
|
|
58
|
+
|
|
59
|
+
# In the loop: N additional queries (one per product)
|
|
60
|
+
for p in products:
|
|
61
|
+
print(p.category.name) # category_id → SELECT * FROM category WHERE id = ?
|
|
62
|
+
|
|
63
|
+
# Total: 1 + N queries
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Solution: select_related and prefetch_related
|
|
67
|
+
|
|
68
|
+
| Method | Use | SQL Query |
|
|
69
|
+
| ---------------------------------- | -------------------------- | ------------------------ |
|
|
70
|
+
| `select_related` | FK one-to-one or ManyToOne | Automatic JOIN |
|
|
71
|
+
| `prefetch_related` | Reverse FK or ManyToMany | 2 separate queries |
|
|
72
|
+
| `prefetch_related` with `Prefetch` | Custom queryset | Filtered in nested query |
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
# FK traversal — use select_related
|
|
76
|
+
products = Product.objects.select_related("category", "tax")
|
|
77
|
+
|
|
78
|
+
# Multiple FK
|
|
79
|
+
products = Product.objects.select_related("category", "tax", "supplier")
|
|
80
|
+
|
|
81
|
+
# Reverse FK (one-to-many) — use prefetch_related
|
|
82
|
+
categories = Category.objects.prefetch_related("products")
|
|
83
|
+
|
|
84
|
+
# ManyToMany — prefetch_related
|
|
85
|
+
product = Product.objects.prefetch_related("tags").first()
|
|
86
|
+
|
|
87
|
+
# Custom prefetch with filter
|
|
88
|
+
from django.db.models import Prefetch
|
|
89
|
+
|
|
90
|
+
products = Product.objects.prefetch_related(
|
|
91
|
+
Prefetch(
|
|
92
|
+
"order_items",
|
|
93
|
+
queryset=OrderItem.objects.filter(order__status="paid")
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Query Patterns
|
|
99
|
+
|
|
100
|
+
### 1. Efficient Filtering
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# Basic filtering
|
|
104
|
+
Product.objects.filter(is_active=True, stock__gt=0)
|
|
105
|
+
|
|
106
|
+
# Filtering with Q objects — OR and negations
|
|
107
|
+
from django.db.models import Q
|
|
108
|
+
|
|
109
|
+
Product.objects.filter(
|
|
110
|
+
Q(is_active=True) & (Q(stock__gt=0) | Q(is_service=True))
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Exclusion
|
|
114
|
+
Product.objects.exclude(status="draft")
|
|
115
|
+
|
|
116
|
+
# Filter by nested FK
|
|
117
|
+
Order.objects.filter(employee__store=store)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 2. Annotations and Aggregations
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from django.db.models import Count, Sum, Avg, Max, Min, F, Q, Case, When, Value, CharField
|
|
124
|
+
from django.db.models.functions import Coalesce, Concat
|
|
125
|
+
|
|
126
|
+
# Conditional sum with Coalesce (avoids None)
|
|
127
|
+
Product.objects.annotate(
|
|
128
|
+
total_sold=Coalesce(
|
|
129
|
+
Sum(
|
|
130
|
+
"order_items__quantity",
|
|
131
|
+
filter=Q(
|
|
132
|
+
order_items__order__status__in=["paid", "shipped"],
|
|
133
|
+
order_items__order__created_at__gte=cutoff,
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
0,
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Use F for arithmetic operations
|
|
141
|
+
Product.objects.update(stock=F("stock") - 1)
|
|
142
|
+
|
|
143
|
+
# Annotation with Case/When for conditional logic
|
|
144
|
+
Product.objects.annotate(
|
|
145
|
+
status_flag=Case(
|
|
146
|
+
When(stock__lte=0, then=Value("out_of_stock")),
|
|
147
|
+
When(stock__lte=10, then=Value("low_stock")),
|
|
148
|
+
default=Value("available"),
|
|
149
|
+
output_field=CharField(),
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Aggregation with filter
|
|
154
|
+
Store.objects.annotate(
|
|
155
|
+
paid_orders=Count("orders", filter=Q(orders__status="paid")),
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3. Exists vs Count
|
|
160
|
+
|
|
161
|
+
**Golden rule**: For existence checks, use `Exists`, never `Count`.
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from django.db.models import Exists, OuterRef, Count
|
|
165
|
+
|
|
166
|
+
# WRONG — loads full count
|
|
167
|
+
products = Product.objects.annotate(
|
|
168
|
+
order_count=Count("order_items")
|
|
169
|
+
).filter(order_count__gt=0)
|
|
170
|
+
|
|
171
|
+
# CORRECT — short-circuits at first match
|
|
172
|
+
products = Product.objects.annotate(
|
|
173
|
+
has_orders=Exists(OrderItem.objects.filter(product=OuterRef("pk")))
|
|
174
|
+
).filter(has_orders=True)
|
|
175
|
+
|
|
176
|
+
# With more complex subquery
|
|
177
|
+
from django.db.models import Subquery
|
|
178
|
+
|
|
179
|
+
latest_order = Order.objects.filter(
|
|
180
|
+
customer=OuterRef("customer_id")
|
|
181
|
+
).order_by("-created_at")
|
|
182
|
+
|
|
183
|
+
Customer.objects.annotate(
|
|
184
|
+
last_order_date=Subquery(
|
|
185
|
+
latest_order.values("created_at")[:1]
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 4. Ordering
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
# Basic ordering
|
|
194
|
+
Product.objects.order_by("name")
|
|
195
|
+
|
|
196
|
+
# Ordering with NullsFirst/NullsLast
|
|
197
|
+
Product.objects.order_by(F("price").nulls_last())
|
|
198
|
+
|
|
199
|
+
# Ordering by annotation
|
|
200
|
+
Store.objects.annotate(
|
|
201
|
+
order_count=Count("orders")
|
|
202
|
+
).order_by("-order_count")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Write Operations
|
|
206
|
+
|
|
207
|
+
### 5. Bulk Operations — Anti-N+1
|
|
208
|
+
|
|
209
|
+
**Never do `save()` inside a loop.** Build lists and use bulk operations:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
# Get with lock once
|
|
213
|
+
products = {p.id: p for p in Product.objects.select_for_update().filter(id__in=ids)}
|
|
214
|
+
|
|
215
|
+
order_items_to_create = []
|
|
216
|
+
products_to_update = []
|
|
217
|
+
|
|
218
|
+
for item in cart:
|
|
219
|
+
product = products[item["id"]]
|
|
220
|
+
product.stock -= item["quantity"]
|
|
221
|
+
order_items_to_create.append(
|
|
222
|
+
OrderItem(order=order, product=product, quantity=item["quantity"], ...)
|
|
223
|
+
)
|
|
224
|
+
products_to_update.append(product)
|
|
225
|
+
|
|
226
|
+
# Bulk create and update
|
|
227
|
+
OrderItem.objects.bulk_create(order_items_to_create)
|
|
228
|
+
Product.objects.bulk_update(products_to_update, ["stock", "updated_at"])
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Bulk operation table:**
|
|
232
|
+
|
|
233
|
+
| Method | Use case | Returns |
|
|
234
|
+
| ------------------------------------------- | -------------------------- | ----------------- |
|
|
235
|
+
| `bulk_create(items)` | Create multiple records | List of objects |
|
|
236
|
+
| `bulk_update(items, fields)` | Update multiple records | None (in-place) |
|
|
237
|
+
| `bulk_create(items, ignore_conflicts=True)` | Ignore duplicates | List (IDs only) |
|
|
238
|
+
| `update()` | Mass update without return | Count of affected |
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# bulk_create with ignore_conflicts
|
|
242
|
+
Product.objects.bulk_create(new_products, ignore_conflicts=True)
|
|
243
|
+
|
|
244
|
+
# bulk update
|
|
245
|
+
Product.objects.filter(category=cat).update(is_active=False)
|
|
246
|
+
|
|
247
|
+
# update with F expressions
|
|
248
|
+
Product.objects.update(stock=F("stock") - 1)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### 6. Surgical Saves — update_fields
|
|
252
|
+
|
|
253
|
+
**Always pass `update_fields`** when saving a subset of fields:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
# Update single field
|
|
257
|
+
product.stock -= 1
|
|
258
|
+
product.save(update_fields=["stock"])
|
|
259
|
+
|
|
260
|
+
# Update multiple fields
|
|
261
|
+
order.status = "paid"
|
|
262
|
+
order.paid_at = timezone.now()
|
|
263
|
+
order.save(update_fields=["status", "paid_at", "updated_at"])
|
|
264
|
+
|
|
265
|
+
# Don't use in migrations — that uses reconstructor
|
|
266
|
+
# DO use in application code
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Why?** Prevents:
|
|
270
|
+
|
|
271
|
+
- Accidental image reprocessing
|
|
272
|
+
- Unnecessary signals
|
|
273
|
+
- Race conditions on unrelated fields
|
|
274
|
+
|
|
275
|
+
### 7. Transactions
|
|
276
|
+
|
|
277
|
+
Use `transaction.atomic()` as a context manager, never as a decorator:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
from django.db import transaction
|
|
281
|
+
|
|
282
|
+
# CORRECT — clear context, automatic error handling
|
|
283
|
+
with transaction.atomic():
|
|
284
|
+
order.save()
|
|
285
|
+
OrderItem.objects.bulk_create(items)
|
|
286
|
+
Product.objects.bulk_update(products, ["stock"])
|
|
287
|
+
cart.clear()
|
|
288
|
+
|
|
289
|
+
# With select_for_update inside the transaction
|
|
290
|
+
with transaction.atomic():
|
|
291
|
+
product = Product.objects.select_for_update().get(pk=product_id)
|
|
292
|
+
product.stock -= quantity
|
|
293
|
+
product.save(update_fields=["stock", "updated_at"])
|
|
294
|
+
|
|
295
|
+
# WRONG — decorator hides intent
|
|
296
|
+
@transaction.atomic
|
|
297
|
+
def create_order(...):
|
|
298
|
+
...
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Isolation levels:**
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
from django.db import transaction
|
|
305
|
+
|
|
306
|
+
# Serializable — maximum isolation
|
|
307
|
+
with transaction.atomic():
|
|
308
|
+
...
|
|
309
|
+
|
|
310
|
+
# Read committed (default) — may cause dirty reads on Edge
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Multi-Tenant and Upload Paths
|
|
314
|
+
|
|
315
|
+
### 8. Upload Paths with Schema
|
|
316
|
+
|
|
317
|
+
Every `FileField`/`ImageField` must include `connection.schema_name` to isolate
|
|
318
|
+
files per tenant:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
import os
|
|
322
|
+
from django.db import connection
|
|
323
|
+
|
|
324
|
+
def get_upload_path(instance, filename):
|
|
325
|
+
schema = connection.schema_name
|
|
326
|
+
ext = filename.split(".")[-1].lower()
|
|
327
|
+
safe_name = f"{uuid4().hex}.{ext}"
|
|
328
|
+
return os.path.join(schema, "products", safe_name)
|
|
329
|
+
|
|
330
|
+
def get_thumb_path(instance, filename):
|
|
331
|
+
schema = connection.schema_name
|
|
332
|
+
return os.path.join(schema, "products", "thumbs", filename)
|
|
333
|
+
|
|
334
|
+
class Product(models.Model):
|
|
335
|
+
image = models.ImageField(upload_to=get_upload_path, storage=S3Storage())
|
|
336
|
+
thumbnail = models.ImageField(upload_to=get_thumb_path, blank=True)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Advanced Queries
|
|
340
|
+
|
|
341
|
+
### 9. Subqueries
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from django.db.models import Subquery, OuterRef
|
|
345
|
+
|
|
346
|
+
# Subquery to get last value
|
|
347
|
+
latest_price = ProductPrice.objects.filter(
|
|
348
|
+
product=OuterRef("pk")
|
|
349
|
+
).order_by("-valid_from").values("price")[:1]
|
|
350
|
+
|
|
351
|
+
Product.objects.annotate(current_price=Subquery(latest_price))
|
|
352
|
+
|
|
353
|
+
# Subquery with aggregate
|
|
354
|
+
order_total = OrderItem.objects.filter(
|
|
355
|
+
order=OuterRef("pk")
|
|
356
|
+
).values("order").annotate(total=Sum("subtotal")).values("total")
|
|
357
|
+
|
|
358
|
+
Order.objects.annotate(order_total=Subquery(order_total))
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 10. Raw Queries — WHEN TO USE
|
|
362
|
+
|
|
363
|
+
Avoid raw queries unless necessary. Use when:
|
|
364
|
+
|
|
365
|
+
- Complex aggregation functions not supported by ORM
|
|
366
|
+
- Queries with multiple JOINs manually optimized
|
|
367
|
+
- Very complex queries where ORM generates inefficient SQL
|
|
368
|
+
|
|
369
|
+
```python
|
|
370
|
+
# With safe parameters (never string interpolation!)
|
|
371
|
+
Product.objects.raw(
|
|
372
|
+
"SELECT * FROM catalog_product WHERE tsvector @@ plainto_tsquery(%s)",
|
|
373
|
+
[search_term]
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# With cursor for complex cases
|
|
377
|
+
from django.db import connection
|
|
378
|
+
|
|
379
|
+
with connection.cursor() as cursor:
|
|
380
|
+
cursor.execute("SELECT ...", [params])
|
|
381
|
+
results = cursor.fetchall()
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Migration Commands
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# Always migrate both schemas
|
|
388
|
+
uv run python manage.py migrate_schemas --shared
|
|
389
|
+
uv run python manage.py migrate_schemas --tenant
|
|
390
|
+
|
|
391
|
+
# Check for pending migrations
|
|
392
|
+
make verifymigrations
|
|
393
|
+
|
|
394
|
+
# Create specific migration
|
|
395
|
+
uv run python manage.py makemigrations catalog --name add_thumbnail
|
|
396
|
+
|
|
397
|
+
# Shows SQL without running
|
|
398
|
+
uv run python manage.py migrate --fake catalog 0003
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Common Mistakes and How to Avoid Them
|
|
402
|
+
|
|
403
|
+
### N+1 in templates
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
# WRONG — N queries
|
|
407
|
+
{% for product in products %}
|
|
408
|
+
{{ product.category.name }}
|
|
409
|
+
{% endfor %}
|
|
410
|
+
|
|
411
|
+
# CORRECT — 1 query with select_related
|
|
412
|
+
products = Product.objects.select_related("category")
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### ForeignKey without related_name
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
# WRONG
|
|
419
|
+
class Order(models.Model):
|
|
420
|
+
customer = models.ForeignKey("users.User", on_delete=...)
|
|
421
|
+
|
|
422
|
+
# CORRECT — always add related_name
|
|
423
|
+
class Order(models.Model):
|
|
424
|
+
customer = models.ForeignKey(
|
|
425
|
+
"users.User",
|
|
426
|
+
on_delete=models.CASCADE,
|
|
427
|
+
related_name="orders",
|
|
428
|
+
)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Queries in signals
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
# WRONG — signal makes additional query
|
|
435
|
+
@receiver(post_save, sender=Order)
|
|
436
|
+
def on_order_save(sender, instance, **kwargs):
|
|
437
|
+
customer = instance.customer # Additional query!
|
|
438
|
+
send_email(customer.email, ...)
|
|
439
|
+
|
|
440
|
+
# CORRECT — pass the object, not the ID
|
|
441
|
+
@receiver(post_save, sender=Order)
|
|
442
|
+
def on_order_save(sender, instance, created, **kwargs):
|
|
443
|
+
if created:
|
|
444
|
+
customer = instance.customer
|
|
445
|
+
send_email(customer.email, ...)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Resources
|
|
449
|
+
|
|
450
|
+
- **Bulk write pattern**: `apps/orders/services.py`, `apps/pos/views.py` lines
|
|
451
|
+
216-246
|
|
452
|
+
- **Annotation examples**: `apps/pos/views.py` lines 59-75
|
|
453
|
+
- **Exists usage**: `apps/catalog/views.py` line 112
|
|
454
|
+
- **Upload paths**: `apps/catalog/models.py`
|
|
455
|
+
- **Django ORM docs**: https://docs.djangoproject.com/en/5.2/topics/db/queries/
|