@dv.nghiem/flowdeck 0.2.3 → 0.3.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/README.md +24 -41
- package/dist/hooks/memory-hook.d.ts +21 -0
- package/dist/hooks/memory-hook.d.ts.map +1 -0
- package/dist/hooks/orchestrator-guard-hook.d.ts +2 -1
- package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
- package/dist/hooks/todo-hook.d.ts +1 -7
- package/dist/hooks/todo-hook.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +649 -310
- package/dist/services/memory-store.d.ts +40 -0
- package/dist/services/memory-store.d.ts.map +1 -0
- package/dist/services/telemetry.d.ts +1 -1
- package/dist/services/telemetry.d.ts.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -0
- package/dist/tools/memory-search.d.ts.map +1 -0
- package/docs/commands/fd-doctor.md +21 -0
- package/docs/commands/fd-quick.md +33 -0
- package/docs/commands/fd-reflect.md +23 -0
- package/docs/commands/fd-status.md +31 -0
- package/docs/commands/fd-translate-intent.md +17 -0
- package/docs/commands.md +209 -271
- package/docs/configuration.md +2 -1
- package/docs/index.md +22 -28
- package/docs/memory.md +69 -0
- package/docs/quick-start.md +1 -1
- package/docs/workflows.md +72 -320
- package/package.json +1 -2
- package/src/commands/fd-deploy-check.md +189 -34
- package/src/commands/fd-discuss.md +44 -6
- package/src/commands/fd-fix-bug.md +47 -20
- package/src/commands/fd-map-codebase.md +66 -18
- package/src/commands/fd-multi-repo.md +130 -6
- package/src/commands/fd-new-feature.md +164 -21
- package/src/commands/fd-new-project.md +14 -1
- package/src/commands/fd-plan.md +66 -44
- package/src/commands/fd-quick.md +60 -0
- package/src/commands/fd-reflect.md +41 -2
- package/src/commands/fd-status.md +84 -0
- package/src/commands/fd-write-docs.md +55 -23
- package/src/rules/README.md +8 -7
- package/src/skills/agent-harness-construction/SKILL.md +227 -0
- package/src/skills/api-design/SKILL.md +5 -0
- package/src/skills/backend-patterns/SKILL.md +105 -0
- package/src/skills/clean-architecture/SKILL.md +85 -0
- package/src/skills/cqrs/SKILL.md +230 -0
- package/src/skills/ddd-architecture/SKILL.md +104 -0
- package/src/skills/django-patterns/SKILL.md +304 -0
- package/src/skills/django-tdd/SKILL.md +297 -0
- package/src/skills/event-driven-architecture/SKILL.md +152 -0
- package/src/skills/frontend-pattern/SKILL.md +159 -0
- package/src/skills/hexagonal-architecture/SKILL.md +80 -0
- package/src/skills/layered-architecture/SKILL.md +64 -0
- package/src/skills/postgres-patterns/SKILL.md +74 -0
- package/src/skills/python-patterns/SKILL.md +5 -0
- package/src/skills/saga-architecture/SKILL.md +113 -0
- package/dist/tools/run-parallel.d.ts +0 -4
- package/dist/tools/run-parallel.d.ts.map +0 -1
- package/docs/command-migration.md +0 -175
- package/docs/commands/fd-analyze-change.md +0 -107
- package/docs/commands/fd-dashboard.md +0 -11
- package/docs/commands/fd-evaluate-risk.md +0 -134
- package/docs/commands/fd-guarded-edit.md +0 -105
- package/docs/commands/fd-progress.md +0 -11
- package/docs/commands/fd-review-code.md +0 -29
- package/docs/commands/fd-roadmap.md +0 -10
- package/docs/commands/fd-settings.md +0 -10
- package/docs/parallel-execution.md +0 -227
- package/src/commands/fd-analyze-change.md +0 -57
- package/src/commands/fd-approve.md +0 -64
- package/src/commands/fd-blast-radius.md +0 -49
- package/src/commands/fd-dashboard.md +0 -57
- package/src/commands/fd-evaluate-risk.md +0 -62
- package/src/commands/fd-guarded-edit.md +0 -69
- package/src/commands/fd-impact-radar.md +0 -51
- package/src/commands/fd-learn.md +0 -36
- package/src/commands/fd-progress.md +0 -50
- package/src/commands/fd-regression-predict.md +0 -57
- package/src/commands/fd-review-code.md +0 -62
- package/src/commands/fd-review-route.md +0 -54
- package/src/commands/fd-roadmap.md +0 -46
- package/src/commands/fd-settings.md +0 -57
- package/src/commands/fd-test-gap.md +0 -54
- package/src/commands/fd-volatility-map.md +0 -64
- package/src/commands/fd-workspace-status.md +0 -34
- package/src/skills/parallel-execute/SKILL.md +0 -92
- package/src/workflows/debug-flow.md +0 -119
- package/src/workflows/deploy-check-flow.md +0 -98
- package/src/workflows/discuss-flow.md +0 -97
- package/src/workflows/execute-flow.md +0 -233
- package/src/workflows/execute-phase.md +0 -145
- package/src/workflows/fix-bug-flow.md +0 -210
- package/src/workflows/map-codebase-flow.md +0 -92
- package/src/workflows/multi-repo-flow.md +0 -226
- package/src/workflows/parallel-execution-flow.md +0 -236
- package/src/workflows/plan-flow.md +0 -126
- package/src/workflows/plan-phase.md +0 -101
- package/src/workflows/refactor-flow.md +0 -122
- package/src/workflows/review-code-flow.md +0 -105
- package/src/workflows/spec-driven-flow.md +0 -43
- package/src/workflows/write-docs-flow.md +0 -95
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# CQRS (Command Query Responsibility Segregation)
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
|
|
5
|
+
Activate when:
|
|
6
|
+
- Designing read-heavy or write-heavy systems separately
|
|
7
|
+
- Implementing complex domain models with divergent read/write logic
|
|
8
|
+
- Building systems that need different data representations for reading vs. writing
|
|
9
|
+
- Scaling read and write workloads independently
|
|
10
|
+
- Implementing event sourcing alongside specialized read models
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
### 1. Separate Command and Query Models
|
|
15
|
+
|
|
16
|
+
| Aspect | Command | Query |
|
|
17
|
+
|--------|---------|-------|
|
|
18
|
+
| Purpose | Modify state | Read state |
|
|
19
|
+
| Returns | Void / ACK | Data |
|
|
20
|
+
| Side Effects | Yes | No |
|
|
21
|
+
| Complexity | Business logic | Data shaping |
|
|
22
|
+
|
|
23
|
+
Commands and queries should use **different models** with different schemas optimized for their specific use case.
|
|
24
|
+
|
|
25
|
+
### 2. Design Command Side
|
|
26
|
+
|
|
27
|
+
- Commands are **intent-based** (present tense: `PlaceOrder`, `UpdatePrice`)
|
|
28
|
+
- Validate business rules **before** executing
|
|
29
|
+
- Return success/failure, not data
|
|
30
|
+
- Keep command handlers small and focused
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
interface Command {
|
|
34
|
+
id: string; // Correlation ID
|
|
35
|
+
type: string; // Command type
|
|
36
|
+
payload: unknown; // Command data
|
|
37
|
+
metadata: {
|
|
38
|
+
userId: string;
|
|
39
|
+
timestamp: string;
|
|
40
|
+
correlationId: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface CommandHandler<T extends Command> {
|
|
45
|
+
execute(command: T): Promise<CommandResult>;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Design Query Side
|
|
50
|
+
|
|
51
|
+
- Queries are **data-focused** (past/read tense: `GetUserOrders`, `FindActiveProducts`)
|
|
52
|
+
- Queries should be **side-effect free**
|
|
53
|
+
- Return **read-optimized** data structures (possibly denormalized)
|
|
54
|
+
- Support pagination, filtering, sorting
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
interface Query {
|
|
58
|
+
id: string;
|
|
59
|
+
type: string;
|
|
60
|
+
parameters: Record<string, unknown>;
|
|
61
|
+
pagination?: { page: number; limit: number };
|
|
62
|
+
sorting?: { field: string; direction: 'asc' | 'desc' }[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface QueryHandler<T extends Query> {
|
|
66
|
+
execute(query: T): Promise<QueryResult>;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 4. Implement Synchronization
|
|
71
|
+
|
|
72
|
+
When commands and queries share data:
|
|
73
|
+
|
|
74
|
+
1. **Synchronous** (same DB): Update the read model transactionally
|
|
75
|
+
2. **Asynchronous** (event-driven): Project events to read models
|
|
76
|
+
3. **Dual writes**: Update both models, handle eventual consistency
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Synchronous synchronization
|
|
80
|
+
async function placeOrder(command: PlaceOrderCommand): Promise<void> {
|
|
81
|
+
const order = Order.create(command.payload);
|
|
82
|
+
|
|
83
|
+
await this.transactionManager.execute(async (tx) => {
|
|
84
|
+
// Write to command model
|
|
85
|
+
await this.orderRepo.save(order, tx);
|
|
86
|
+
|
|
87
|
+
// Synchronize to read model
|
|
88
|
+
const readModel = {
|
|
89
|
+
orderId: order.id,
|
|
90
|
+
customerId: order.customerId,
|
|
91
|
+
status: order.status,
|
|
92
|
+
total: order.total,
|
|
93
|
+
placedAt: order.placedAt
|
|
94
|
+
};
|
|
95
|
+
await this.orderReadRepo.save(readModel, tx);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 5. Handle Eventual Consistency
|
|
101
|
+
|
|
102
|
+
If read and write models are updated asynchronously:
|
|
103
|
+
|
|
104
|
+
- Document **expected consistency lag**
|
|
105
|
+
- Design UIs to handle stale data gracefully
|
|
106
|
+
- Implement **cache invalidation** strategies
|
|
107
|
+
- Use **version numbers** or timestamps for cache validation
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
### Command Implementation
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// commands/place-order.command.ts
|
|
115
|
+
interface PlaceOrderCommand {
|
|
116
|
+
orderId?: string; // Optional, generated if not provided
|
|
117
|
+
customerId: string;
|
|
118
|
+
items: OrderItem[];
|
|
119
|
+
paymentMethod: 'CARD' | 'PAYPAL';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class PlaceOrderCommandHandler implements CommandHandler<PlaceOrderCommand> {
|
|
123
|
+
async execute(command: PlaceOrderCommand): Promise<CommandResult> {
|
|
124
|
+
// 1. Validate command
|
|
125
|
+
const validation = this.validate(command);
|
|
126
|
+
if (!validation.success) {
|
|
127
|
+
return CommandResult.failure(validation.errors);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 2. Check business invariants
|
|
131
|
+
const customer = await this.customerRepo.findById(command.customerId);
|
|
132
|
+
if (!customer.isActive) {
|
|
133
|
+
return CommandResult.failure('Customer account is not active');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 3. Create aggregate
|
|
137
|
+
const order = Order.create({
|
|
138
|
+
id: command.orderId,
|
|
139
|
+
customerId: command.customerId,
|
|
140
|
+
items: command.items
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 4. Persist
|
|
144
|
+
await this.orderRepo.save(order);
|
|
145
|
+
|
|
146
|
+
// 5. Emit event for async processing
|
|
147
|
+
await this.eventBus.publish(OrderPlacedEvent.fromOrder(order));
|
|
148
|
+
|
|
149
|
+
return CommandResult.success({ orderId: order.id });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Query Implementation
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// queries/get-order-details.query.ts
|
|
158
|
+
interface GetOrderDetailsQuery {
|
|
159
|
+
orderId: string;
|
|
160
|
+
includeItems?: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
interface OrderDetailsReadModel {
|
|
164
|
+
orderId: string;
|
|
165
|
+
customerId: string;
|
|
166
|
+
customerName: string;
|
|
167
|
+
status: string;
|
|
168
|
+
total: number;
|
|
169
|
+
placedAt: string;
|
|
170
|
+
items?: OrderItemReadModel[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
class GetOrderDetailsQueryHandler implements QueryHandler<GetOrderDetailsQuery, OrderDetailsReadModel> {
|
|
174
|
+
async execute(query: GetOrderDetailsQuery): Promise<OrderDetailsReadModel> {
|
|
175
|
+
const order = await this.readModelRepo.findOrderWithDetails(query.orderId);
|
|
176
|
+
|
|
177
|
+
if (!order) {
|
|
178
|
+
throw new QueryNotFoundError('Order not found');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result: OrderDetailsReadModel = {
|
|
182
|
+
orderId: order.orderId,
|
|
183
|
+
customerId: order.customerId,
|
|
184
|
+
customerName: order.customerName,
|
|
185
|
+
status: order.status,
|
|
186
|
+
total: order.total,
|
|
187
|
+
placedAt: order.placedAt
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
if (query.includeItems) {
|
|
191
|
+
result.items = await this.readModelRepo.findOrderItems(query.orderId);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Mediator Pattern for CQRS
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
class CqrsMediator {
|
|
203
|
+
private commandHandlers: Map<string, CommandHandler<any>>;
|
|
204
|
+
private queryHandlers: Map<string, QueryHandler<any>>;
|
|
205
|
+
|
|
206
|
+
async send<T>(message: Command | Query): Promise<CommandResult | QueryResult> {
|
|
207
|
+
const handler = message instanceof Command
|
|
208
|
+
? this.commandHandlers.get(message.type)
|
|
209
|
+
: this.queryHandlers.get(message.type);
|
|
210
|
+
|
|
211
|
+
if (!handler) {
|
|
212
|
+
throw new HandlerNotFoundError(message.type);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return handler.execute(message);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Usage
|
|
220
|
+
const result = await mediator.send(new PlaceOrderCommand({ ... }));
|
|
221
|
+
const orderDetails = await mediator.send(new GetOrderDetailsQuery({ orderId: '123' }));
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Related Skills
|
|
225
|
+
|
|
226
|
+
- api-design
|
|
227
|
+
- event-driven-architecture
|
|
228
|
+
- backend-patterns
|
|
229
|
+
- event-sourcing
|
|
230
|
+
- hexagonal-architecture
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# ddd-architecture
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
When modeling complex business domains where deep understanding of the problem space, ubiquitous language, and bounded contexts are critical for long-term maintainability.
|
|
5
|
+
|
|
6
|
+
## Steps
|
|
7
|
+
1. **Establish the bounded context** - Identify the explicit boundary within which a single model (ubiquitous language) holds.
|
|
8
|
+
2. **Build the domain model** - Create entities, value objects, aggregates, and domain events that reflect real business concepts.
|
|
9
|
+
3. **Define aggregates** - Group related entities and value objects under a root aggregate that enforces invariants.
|
|
10
|
+
4. **Identify domain events** - Capture meaningful business occurrences that other parts of the system may need to react to.
|
|
11
|
+
5. **Create domain services** - Model operations that don't naturally belong to a single entity or value object.
|
|
12
|
+
6. **Define repository interfaces** - Create ports for persisting and retrieving aggregates (implementation is infrastructure).
|
|
13
|
+
7. **Implement application services** - Orchestrate the domain model, handle transactions, and coordinate multiple aggregates.
|
|
14
|
+
8. **Establish anti-corruption layers** - Translate between external systems (legacy, third-party) and your domain model.
|
|
15
|
+
|
|
16
|
+
## Examples
|
|
17
|
+
```typescript
|
|
18
|
+
// Value Object - Immutable concept with equality
|
|
19
|
+
class Money {
|
|
20
|
+
constructor(
|
|
21
|
+
public readonly amount: number,
|
|
22
|
+
public readonly currency: Currency
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
static of(amount: number, currency: Currency): Money {
|
|
26
|
+
return new Money(Math.round(amount * 100) / 100, currency)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
add(other: Money): Money {
|
|
30
|
+
if (this.currency !== other.currency) {
|
|
31
|
+
throw new Error('Currency mismatch')
|
|
32
|
+
}
|
|
33
|
+
return Money.of(this.amount + other.amount, this.currency)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Aggregate Root - Enforces invariants for the aggregate
|
|
38
|
+
class Order extends AggregateRoot {
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly id: OrderId,
|
|
41
|
+
private readonly customer: Customer,
|
|
42
|
+
private items: OrderItem[],
|
|
43
|
+
private status: OrderStatus
|
|
44
|
+
) {
|
|
45
|
+
super()
|
|
46
|
+
this.validate()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private validate(): void {
|
|
50
|
+
if (this.items.length === 0) {
|
|
51
|
+
throw new DomainException('Order must have at least one item')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get total(): Money {
|
|
56
|
+
return this.items.reduce(
|
|
57
|
+
(sum, item) => sum.add(item.subtotal),
|
|
58
|
+
Money.of(0, Currency.USD)
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Business methods that enforce invariants
|
|
63
|
+
addItem(item: OrderItem): void {
|
|
64
|
+
if (this.status !== OrderStatus.DRAFT) {
|
|
65
|
+
throw new DomainException('Cannot add items to a non-draft order')
|
|
66
|
+
}
|
|
67
|
+
this.items.push(item)
|
|
68
|
+
this.addDomainEvent(new OrderItemAddedEvent(this.id, item))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
submit(): void {
|
|
72
|
+
if (!this.canSubmit()) {
|
|
73
|
+
throw new DomainException('Order cannot be submitted')
|
|
74
|
+
}
|
|
75
|
+
this.status = OrderStatus.SUBMITTED
|
|
76
|
+
this.addDomainEvent(new OrderSubmittedEvent(this))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private canSubmit(): boolean {
|
|
80
|
+
return this.status === OrderStatus.DRAFT && this.items.length > 0
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Domain Event - Business facts that may trigger reactions
|
|
85
|
+
class OrderSubmittedEvent extends DomainEvent {
|
|
86
|
+
constructor(public readonly order: Order) {
|
|
87
|
+
super('order.submitted', order.id)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Repository Interface (Port) - Persistence abstraction
|
|
92
|
+
interface OrderRepository {
|
|
93
|
+
findById(id: OrderId): Promise<Order | null>
|
|
94
|
+
findByCustomer(customerId: CustomerId): Promise<Order[]>
|
|
95
|
+
save(order: Order): Promise<void>
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Related Skills
|
|
100
|
+
- clean-architecture
|
|
101
|
+
- hexagonal-architecture
|
|
102
|
+
- layered-architecture
|
|
103
|
+
- saga-architecture
|
|
104
|
+
- backend-patterns
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: django-patterns
|
|
3
|
+
description: Django patterns covering models, ORM queries, views, class-based views, middleware, URL routing, forms, and project structure. Activate when writing or reviewing Django code.
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Django Patterns Skill
|
|
8
|
+
|
|
9
|
+
Idiomatic Django for production systems. Covers models, views, ORM patterns, and project layout.
|
|
10
|
+
|
|
11
|
+
## When to Activate
|
|
12
|
+
|
|
13
|
+
Activate when:
|
|
14
|
+
- Writing new Django apps or services
|
|
15
|
+
- Reviewing Django code for correctness and idiom
|
|
16
|
+
- Designing model relationships and ORM queries
|
|
17
|
+
- Building views with class-based views or function-based views
|
|
18
|
+
- Configuring URL routing and middleware
|
|
19
|
+
|
|
20
|
+
## Project Structure
|
|
21
|
+
|
|
22
|
+
### Standard Layout
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
manage.py
|
|
26
|
+
mysite/
|
|
27
|
+
__init__.py
|
|
28
|
+
settings.py
|
|
29
|
+
urls.py
|
|
30
|
+
wsgi.py
|
|
31
|
+
myapp/
|
|
32
|
+
__init__.py
|
|
33
|
+
models.py
|
|
34
|
+
views.py
|
|
35
|
+
urls.py
|
|
36
|
+
admin.py
|
|
37
|
+
apps.py
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Decoupled Layout (Recommended)
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
manage.py
|
|
44
|
+
myapp/
|
|
45
|
+
__init__.py
|
|
46
|
+
models.py
|
|
47
|
+
views.py
|
|
48
|
+
urls.py
|
|
49
|
+
mysite/
|
|
50
|
+
__init__.py
|
|
51
|
+
settings.py
|
|
52
|
+
urls.py
|
|
53
|
+
wsgi.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Import apps as top-level modules without project prefix.
|
|
57
|
+
|
|
58
|
+
## Models and ORM
|
|
59
|
+
|
|
60
|
+
### Basic Model Definition
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from django.db import models
|
|
64
|
+
|
|
65
|
+
class Reporter(models.Model):
|
|
66
|
+
full_name = models.CharField(max_length=70)
|
|
67
|
+
|
|
68
|
+
def __str__(self):
|
|
69
|
+
return self.full_name
|
|
70
|
+
|
|
71
|
+
class Article(models.Model):
|
|
72
|
+
pub_date = models.DateField()
|
|
73
|
+
headline = models.CharField(max_length=200)
|
|
74
|
+
content = models.TextField()
|
|
75
|
+
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
|
|
76
|
+
|
|
77
|
+
def __str__(self):
|
|
78
|
+
return self.headline
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Field Types and Options
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
class Book(models.Model):
|
|
85
|
+
class Status(models.TextChoices):
|
|
86
|
+
DRAFT = 'draft', 'Draft'
|
|
87
|
+
PUBLISHED = 'published', 'Published'
|
|
88
|
+
ARCHIVED = 'archived', 'Archived'
|
|
89
|
+
|
|
90
|
+
title = models.CharField(max_length=200)
|
|
91
|
+
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='books')
|
|
92
|
+
isbn = models.CharField(max_length=13, unique=True)
|
|
93
|
+
published_date = models.DateField()
|
|
94
|
+
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
95
|
+
status = models.CharField(max_length=10, choices=Status.choices, default=Status.DRAFT)
|
|
96
|
+
tags = models.ManyToManyField('Tag', blank=True)
|
|
97
|
+
|
|
98
|
+
class Meta:
|
|
99
|
+
ordering = ['title']
|
|
100
|
+
indexes = [
|
|
101
|
+
models.Index(fields=['title', 'author']),
|
|
102
|
+
models.Index(fields=['published_date']),
|
|
103
|
+
]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### QuerySet Operations
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
# Create
|
|
110
|
+
author = Author.objects.create(name="Jane Doe", email="jane@example.com")
|
|
111
|
+
book = Book.objects.create(title="Django Mastery", author=author, isbn="9781234567890")
|
|
112
|
+
|
|
113
|
+
# Read with filtering
|
|
114
|
+
published_books = Book.objects.filter(status=Book.Status.PUBLISHED)
|
|
115
|
+
expensive_books = Book.objects.filter(price__gte=30.00)
|
|
116
|
+
|
|
117
|
+
# Complex queries with Q objects
|
|
118
|
+
from django.db.models import Q, F, Count, Avg
|
|
119
|
+
books = Book.objects.filter(
|
|
120
|
+
Q(title__icontains='django') | Q(author__name__icontains='django')
|
|
121
|
+
).exclude(status=Book.Status.ARCHIVED)
|
|
122
|
+
|
|
123
|
+
# Aggregations
|
|
124
|
+
author_stats = Author.objects.annotate(
|
|
125
|
+
book_count=Count('books'),
|
|
126
|
+
avg_price=Avg('books__price')
|
|
127
|
+
).filter(book_count__gt=0)
|
|
128
|
+
|
|
129
|
+
# Update with F expressions
|
|
130
|
+
Book.objects.filter(pk=1).update(price=F('price') * 1.1)
|
|
131
|
+
|
|
132
|
+
# Select related (prevents N+1)
|
|
133
|
+
books_with_authors = Book.objects.select_related('author').all()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Views
|
|
137
|
+
|
|
138
|
+
### Class-Based Views
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from django.http import HttpResponse
|
|
142
|
+
from django.views import View
|
|
143
|
+
from django.views.generic import ListView, DetailView, CreateView
|
|
144
|
+
|
|
145
|
+
class MyView(View):
|
|
146
|
+
def get(self, request, *args, **kwargs):
|
|
147
|
+
return HttpResponse("Hello, World!")
|
|
148
|
+
|
|
149
|
+
# URL routing
|
|
150
|
+
from django.urls import path
|
|
151
|
+
urlpatterns = [
|
|
152
|
+
path("about/", MyView.as_view()),
|
|
153
|
+
]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Generic Class-Based Views
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
class ArticleListView(ListView):
|
|
160
|
+
model = Article
|
|
161
|
+
template_name = "articles/list.html"
|
|
162
|
+
context_object_name = "articles"
|
|
163
|
+
|
|
164
|
+
def get_queryset(self):
|
|
165
|
+
return Article.objects.filter(status='published').select_related('author')
|
|
166
|
+
|
|
167
|
+
class ArticleDetailView(DetailView):
|
|
168
|
+
model = Article
|
|
169
|
+
template_name = "articles/detail.html"
|
|
170
|
+
context_object_name = "article"
|
|
171
|
+
|
|
172
|
+
class ArticleCreateView(CreateView):
|
|
173
|
+
model = Article
|
|
174
|
+
fields = ['title', 'content', 'author', 'status']
|
|
175
|
+
template_name = "articles/form.html"
|
|
176
|
+
success_url = reverse_lazy('article-list')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Function-Based Views
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from django.http import JsonResponse
|
|
183
|
+
from django.shortcuts import get_object_or_404
|
|
184
|
+
|
|
185
|
+
def article_detail(request, pk):
|
|
186
|
+
article = get_object_or_404(Article, pk=pk)
|
|
187
|
+
return JsonResponse({
|
|
188
|
+
'id': article.id,
|
|
189
|
+
'title': article.title,
|
|
190
|
+
'content': article.content,
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## URL Routing
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from django.urls import path, include
|
|
198
|
+
|
|
199
|
+
urlpatterns = [
|
|
200
|
+
path("articles/", include("articles.urls")),
|
|
201
|
+
path("about/", AboutView.as_view(), name="about"),
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
# In articles/urls.py
|
|
205
|
+
from django.urls import path
|
|
206
|
+
from .views import ArticleListView, ArticleDetailView
|
|
207
|
+
|
|
208
|
+
urlpatterns = [
|
|
209
|
+
path("", ArticleListView.as_view(), name="article-list"),
|
|
210
|
+
path("<int:pk>/", ArticleDetailView.as_view(), name="article-detail"),
|
|
211
|
+
]
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Middleware
|
|
215
|
+
|
|
216
|
+
### Function-Based Middleware
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
def simple_middleware(get_response):
|
|
220
|
+
def middleware(request):
|
|
221
|
+
# Code executed before view
|
|
222
|
+
response = get_response(request)
|
|
223
|
+
# Code executed after view
|
|
224
|
+
return response
|
|
225
|
+
return middleware
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Adding to Settings
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
MIDDLEWARE = [
|
|
232
|
+
'django.middleware.security.SecurityMiddleware',
|
|
233
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
234
|
+
'django.middleware.common.CommonMiddleware',
|
|
235
|
+
'myapp.middleware.simple_middleware',
|
|
236
|
+
]
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Forms
|
|
240
|
+
|
|
241
|
+
### Model Forms
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from django import forms
|
|
245
|
+
from .models import Article
|
|
246
|
+
|
|
247
|
+
class ArticleForm(forms.ModelForm):
|
|
248
|
+
class Meta:
|
|
249
|
+
model = Article
|
|
250
|
+
fields = ['title', 'content', 'author', 'status']
|
|
251
|
+
|
|
252
|
+
def clean_title(self):
|
|
253
|
+
title = self.cleaned_data['title']
|
|
254
|
+
if 'spam' in title.lower():
|
|
255
|
+
raise forms.ValidationError("No spam allowed")
|
|
256
|
+
return title
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Common Pitfalls
|
|
260
|
+
|
|
261
|
+
### N+1 Query Problem
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
# Bad: causes N+1 queries
|
|
265
|
+
articles = Article.objects.all()
|
|
266
|
+
for article in articles:
|
|
267
|
+
print(article.author.name) # N additional queries
|
|
268
|
+
|
|
269
|
+
# Good: use select_related or prefetch_related
|
|
270
|
+
articles = Article.objects.select_related('author').all()
|
|
271
|
+
for article in articles:
|
|
272
|
+
print(article.author.name) # No additional queries
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Using Q Objects for Complex Queries
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from django.db.models import Q
|
|
279
|
+
|
|
280
|
+
# OR conditions
|
|
281
|
+
Book.objects.filter(Q(title__icontains='django') | Q(author__name__icontains='django'))
|
|
282
|
+
|
|
283
|
+
# AND with exclusion
|
|
284
|
+
Book.objects.filter(status='published').exclude(Q(title__icontains='old'))
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Bulk Operations
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
# Create multiple objects efficiently
|
|
291
|
+
Book.objects.bulk_create([
|
|
292
|
+
Book(title='Book 1', author=author),
|
|
293
|
+
Book(title='Book 2', author=author),
|
|
294
|
+
])
|
|
295
|
+
|
|
296
|
+
# Update multiple objects
|
|
297
|
+
Book.objects.filter(status='archived').update(status='published')
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Related Skills
|
|
301
|
+
|
|
302
|
+
- django-tdd
|
|
303
|
+
- python-patterns
|
|
304
|
+
- api-design
|