autoworkflow 3.1.4 → 3.5.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.
Files changed (123) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +174 -11
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/pre-edit.sh +129 -0
  12. package/.claude/hooks/session-check.sh +79 -0
  13. package/.claude/settings.json +40 -6
  14. package/.claude/settings.local.json +3 -1
  15. package/.claude/skills/actix.md +337 -0
  16. package/.claude/skills/alembic.md +504 -0
  17. package/.claude/skills/angular.md +237 -0
  18. package/.claude/skills/api-design.md +187 -0
  19. package/.claude/skills/aspnet-core.md +377 -0
  20. package/.claude/skills/astro.md +245 -0
  21. package/.claude/skills/auth-clerk.md +327 -0
  22. package/.claude/skills/auth-firebase.md +367 -0
  23. package/.claude/skills/auth-nextauth.md +359 -0
  24. package/.claude/skills/auth-supabase.md +368 -0
  25. package/.claude/skills/axum.md +386 -0
  26. package/.claude/skills/blazor.md +456 -0
  27. package/.claude/skills/chi.md +348 -0
  28. package/.claude/skills/code-review.md +133 -0
  29. package/.claude/skills/csharp.md +296 -0
  30. package/.claude/skills/css-modules.md +325 -0
  31. package/.claude/skills/cypress.md +343 -0
  32. package/.claude/skills/debugging.md +133 -0
  33. package/.claude/skills/diesel.md +392 -0
  34. package/.claude/skills/django.md +301 -0
  35. package/.claude/skills/docker.md +319 -0
  36. package/.claude/skills/doctrine.md +473 -0
  37. package/.claude/skills/documentation.md +182 -0
  38. package/.claude/skills/dotnet.md +409 -0
  39. package/.claude/skills/drizzle.md +293 -0
  40. package/.claude/skills/echo.md +321 -0
  41. package/.claude/skills/eloquent.md +256 -0
  42. package/.claude/skills/emotion.md +426 -0
  43. package/.claude/skills/entity-framework.md +370 -0
  44. package/.claude/skills/express.md +316 -0
  45. package/.claude/skills/fastapi.md +329 -0
  46. package/.claude/skills/fastify.md +299 -0
  47. package/.claude/skills/fiber.md +315 -0
  48. package/.claude/skills/flask.md +322 -0
  49. package/.claude/skills/gin.md +342 -0
  50. package/.claude/skills/git.md +116 -0
  51. package/.claude/skills/github-actions.md +353 -0
  52. package/.claude/skills/go.md +377 -0
  53. package/.claude/skills/gorm.md +409 -0
  54. package/.claude/skills/graphql.md +478 -0
  55. package/.claude/skills/hibernate.md +379 -0
  56. package/.claude/skills/hono.md +306 -0
  57. package/.claude/skills/java.md +400 -0
  58. package/.claude/skills/jest.md +313 -0
  59. package/.claude/skills/jpa.md +282 -0
  60. package/.claude/skills/kotlin.md +347 -0
  61. package/.claude/skills/kubernetes.md +363 -0
  62. package/.claude/skills/laravel.md +414 -0
  63. package/.claude/skills/mcp-browser.md +320 -0
  64. package/.claude/skills/mcp-database.md +219 -0
  65. package/.claude/skills/mcp-fetch.md +241 -0
  66. package/.claude/skills/mcp-filesystem.md +204 -0
  67. package/.claude/skills/mcp-github.md +217 -0
  68. package/.claude/skills/mcp-memory.md +240 -0
  69. package/.claude/skills/mcp-search.md +218 -0
  70. package/.claude/skills/mcp-slack.md +262 -0
  71. package/.claude/skills/micronaut.md +388 -0
  72. package/.claude/skills/mongodb.md +319 -0
  73. package/.claude/skills/mongoose.md +355 -0
  74. package/.claude/skills/mysql.md +281 -0
  75. package/.claude/skills/nestjs.md +335 -0
  76. package/.claude/skills/nextjs-app-router.md +260 -0
  77. package/.claude/skills/nextjs-pages.md +172 -0
  78. package/.claude/skills/nuxt.md +202 -0
  79. package/.claude/skills/openapi.md +489 -0
  80. package/.claude/skills/performance.md +199 -0
  81. package/.claude/skills/php.md +398 -0
  82. package/.claude/skills/playwright.md +371 -0
  83. package/.claude/skills/postgresql.md +257 -0
  84. package/.claude/skills/prisma.md +293 -0
  85. package/.claude/skills/pydantic.md +304 -0
  86. package/.claude/skills/pytest.md +313 -0
  87. package/.claude/skills/python.md +272 -0
  88. package/.claude/skills/quarkus.md +377 -0
  89. package/.claude/skills/react.md +230 -0
  90. package/.claude/skills/redis.md +391 -0
  91. package/.claude/skills/refactoring.md +143 -0
  92. package/.claude/skills/remix.md +246 -0
  93. package/.claude/skills/rest-api.md +490 -0
  94. package/.claude/skills/rocket.md +366 -0
  95. package/.claude/skills/rust.md +341 -0
  96. package/.claude/skills/sass.md +380 -0
  97. package/.claude/skills/sea-orm.md +382 -0
  98. package/.claude/skills/security.md +167 -0
  99. package/.claude/skills/sequelize.md +395 -0
  100. package/.claude/skills/spring-boot.md +416 -0
  101. package/.claude/skills/sqlalchemy.md +269 -0
  102. package/.claude/skills/sqlx-rust.md +408 -0
  103. package/.claude/skills/state-jotai.md +346 -0
  104. package/.claude/skills/state-mobx.md +353 -0
  105. package/.claude/skills/state-pinia.md +431 -0
  106. package/.claude/skills/state-redux.md +337 -0
  107. package/.claude/skills/state-tanstack-query.md +434 -0
  108. package/.claude/skills/state-zustand.md +340 -0
  109. package/.claude/skills/styled-components.md +403 -0
  110. package/.claude/skills/svelte.md +238 -0
  111. package/.claude/skills/sveltekit.md +207 -0
  112. package/.claude/skills/symfony.md +437 -0
  113. package/.claude/skills/tailwind.md +279 -0
  114. package/.claude/skills/terraform.md +394 -0
  115. package/.claude/skills/testing-library.md +371 -0
  116. package/.claude/skills/trpc.md +426 -0
  117. package/.claude/skills/typeorm.md +368 -0
  118. package/.claude/skills/vitest.md +330 -0
  119. package/.claude/skills/vue.md +202 -0
  120. package/.claude/skills/warp.md +365 -0
  121. package/README.md +135 -52
  122. package/package.json +1 -1
  123. package/system/triggers.md +152 -11
@@ -0,0 +1,392 @@
1
+ # Diesel ORM Skill
2
+
3
+ ## Setup and Configuration
4
+ \`\`\`bash
5
+ # Install diesel CLI
6
+ cargo install diesel_cli --no-default-features --features postgres
7
+
8
+ # Initialize diesel
9
+ diesel setup
10
+
11
+ # Create migration
12
+ diesel migration generate create_users
13
+
14
+ # Run migrations
15
+ diesel migration run
16
+
17
+ # Revert migration
18
+ diesel migration revert
19
+ \`\`\`
20
+
21
+ ## Schema Definition (schema.rs - auto-generated)
22
+ \`\`\`rust
23
+ // src/schema.rs - Generated by diesel migration run
24
+ diesel::table! {
25
+ users (id) {
26
+ id -> Uuid,
27
+ email -> Varchar,
28
+ name -> Varchar,
29
+ password_hash -> Varchar,
30
+ is_active -> Bool,
31
+ created_at -> Timestamptz,
32
+ updated_at -> Timestamptz,
33
+ }
34
+ }
35
+
36
+ diesel::table! {
37
+ posts (id) {
38
+ id -> Uuid,
39
+ title -> Varchar,
40
+ content -> Text,
41
+ published -> Bool,
42
+ author_id -> Uuid,
43
+ created_at -> Timestamptz,
44
+ updated_at -> Timestamptz,
45
+ }
46
+ }
47
+
48
+ diesel::table! {
49
+ comments (id) {
50
+ id -> Uuid,
51
+ post_id -> Uuid,
52
+ user_id -> Uuid,
53
+ content -> Text,
54
+ created_at -> Timestamptz,
55
+ }
56
+ }
57
+
58
+ diesel::joinable!(posts -> users (author_id));
59
+ diesel::joinable!(comments -> posts (post_id));
60
+ diesel::joinable!(comments -> users (user_id));
61
+
62
+ diesel::allow_tables_to_appear_in_same_query!(
63
+ users,
64
+ posts,
65
+ comments,
66
+ );
67
+ \`\`\`
68
+
69
+ ## Model Definitions
70
+ \`\`\`rust
71
+ use diesel::prelude::*;
72
+ use chrono::{DateTime, Utc};
73
+ use serde::{Deserialize, Serialize};
74
+ use uuid::Uuid;
75
+
76
+ // Queryable - for SELECT queries
77
+ #[derive(Debug, Clone, Queryable, Selectable, Serialize)]
78
+ #[diesel(table_name = crate::schema::users)]
79
+ pub struct User {
80
+ pub id: Uuid,
81
+ pub email: String,
82
+ pub name: String,
83
+ #[serde(skip_serializing)]
84
+ pub password_hash: String,
85
+ pub is_active: bool,
86
+ pub created_at: DateTime<Utc>,
87
+ pub updated_at: DateTime<Utc>,
88
+ }
89
+
90
+ // Insertable - for INSERT queries
91
+ #[derive(Debug, Insertable)]
92
+ #[diesel(table_name = crate::schema::users)]
93
+ pub struct NewUser {
94
+ pub id: Uuid,
95
+ pub email: String,
96
+ pub name: String,
97
+ pub password_hash: String,
98
+ pub is_active: bool,
99
+ }
100
+
101
+ impl NewUser {
102
+ pub fn new(email: &str, name: &str, password: &str) -> Result<Self, AppError> {
103
+ let password_hash = hash_password(password)?;
104
+ Ok(Self {
105
+ id: Uuid::new_v4(),
106
+ email: email.to_string(),
107
+ name: name.to_string(),
108
+ password_hash,
109
+ is_active: true,
110
+ })
111
+ }
112
+ }
113
+
114
+ // AsChangeset - for UPDATE queries
115
+ #[derive(Debug, AsChangeset)]
116
+ #[diesel(table_name = crate::schema::users)]
117
+ pub struct UpdateUser {
118
+ pub name: Option<String>,
119
+ pub email: Option<String>,
120
+ pub is_active: Option<bool>,
121
+ pub updated_at: DateTime<Utc>,
122
+ }
123
+
124
+ // Associations
125
+ #[derive(Debug, Queryable, Selectable, Associations, Serialize)]
126
+ #[diesel(table_name = crate::schema::posts)]
127
+ #[diesel(belongs_to(User, foreign_key = author_id))]
128
+ pub struct Post {
129
+ pub id: Uuid,
130
+ pub title: String,
131
+ pub content: String,
132
+ pub published: bool,
133
+ pub author_id: Uuid,
134
+ pub created_at: DateTime<Utc>,
135
+ pub updated_at: DateTime<Utc>,
136
+ }
137
+ \`\`\`
138
+
139
+ ## Connection Pool
140
+ \`\`\`rust
141
+ use diesel::pg::PgConnection;
142
+ use diesel::r2d2::{self, ConnectionManager, Pool, PooledConnection};
143
+ use std::env;
144
+
145
+ pub type DbPool = Pool<ConnectionManager<PgConnection>>;
146
+ pub type DbConnection = PooledConnection<ConnectionManager<PgConnection>>;
147
+
148
+ pub fn create_pool() -> DbPool {
149
+ let database_url = env::var("DATABASE_URL")
150
+ .expect("DATABASE_URL must be set");
151
+
152
+ let manager = ConnectionManager::<PgConnection>::new(database_url);
153
+
154
+ Pool::builder()
155
+ .max_size(10)
156
+ .min_idle(Some(2))
157
+ .build(manager)
158
+ .expect("Failed to create pool")
159
+ }
160
+
161
+ // Get connection from pool
162
+ pub fn get_conn(pool: &DbPool) -> Result<DbConnection, AppError> {
163
+ pool.get().map_err(|e| AppError::Database(e.to_string()))
164
+ }
165
+ \`\`\`
166
+
167
+ ## CRUD Operations
168
+ \`\`\`rust
169
+ use diesel::prelude::*;
170
+ use crate::schema::users::dsl::*;
171
+
172
+ pub struct UserRepository;
173
+
174
+ impl UserRepository {
175
+ // Create
176
+ pub fn create(conn: &mut PgConnection, new_user: NewUser) -> Result<User, AppError> {
177
+ diesel::insert_into(users)
178
+ .values(&new_user)
179
+ .returning(User::as_returning())
180
+ .get_result(conn)
181
+ .map_err(|e| AppError::Database(e.to_string()))
182
+ }
183
+
184
+ // Read by ID
185
+ pub fn find_by_id(conn: &mut PgConnection, user_id: Uuid) -> Result<Option<User>, AppError> {
186
+ users
187
+ .filter(id.eq(user_id))
188
+ .select(User::as_select())
189
+ .first(conn)
190
+ .optional()
191
+ .map_err(|e| AppError::Database(e.to_string()))
192
+ }
193
+
194
+ // Read by email
195
+ pub fn find_by_email(conn: &mut PgConnection, user_email: &str) -> Result<Option<User>, AppError> {
196
+ users
197
+ .filter(email.eq(user_email))
198
+ .select(User::as_select())
199
+ .first(conn)
200
+ .optional()
201
+ .map_err(|e| AppError::Database(e.to_string()))
202
+ }
203
+
204
+ // List with pagination
205
+ pub fn list(
206
+ conn: &mut PgConnection,
207
+ page: i64,
208
+ per_page: i64,
209
+ ) -> Result<(Vec<User>, i64), AppError> {
210
+ let offset = (page - 1) * per_page;
211
+
212
+ let results = users
213
+ .filter(is_active.eq(true))
214
+ .order(created_at.desc())
215
+ .offset(offset)
216
+ .limit(per_page)
217
+ .select(User::as_select())
218
+ .load(conn)
219
+ .map_err(|e| AppError::Database(e.to_string()))?;
220
+
221
+ let total: i64 = users
222
+ .filter(is_active.eq(true))
223
+ .count()
224
+ .get_result(conn)
225
+ .map_err(|e| AppError::Database(e.to_string()))?;
226
+
227
+ Ok((results, total))
228
+ }
229
+
230
+ // Update
231
+ pub fn update(
232
+ conn: &mut PgConnection,
233
+ user_id: Uuid,
234
+ changeset: UpdateUser,
235
+ ) -> Result<User, AppError> {
236
+ diesel::update(users.filter(id.eq(user_id)))
237
+ .set(&changeset)
238
+ .returning(User::as_returning())
239
+ .get_result(conn)
240
+ .map_err(|e| AppError::Database(e.to_string()))
241
+ }
242
+
243
+ // Delete
244
+ pub fn delete(conn: &mut PgConnection, user_id: Uuid) -> Result<usize, AppError> {
245
+ diesel::delete(users.filter(id.eq(user_id)))
246
+ .execute(conn)
247
+ .map_err(|e| AppError::Database(e.to_string()))
248
+ }
249
+ }
250
+ \`\`\`
251
+
252
+ ## Complex Queries
253
+ \`\`\`rust
254
+ use diesel::prelude::*;
255
+ use crate::schema::{users, posts, comments};
256
+
257
+ // Join queries
258
+ pub fn get_user_with_posts(
259
+ conn: &mut PgConnection,
260
+ user_id: Uuid,
261
+ ) -> Result<(User, Vec<Post>), AppError> {
262
+ let user = users::table
263
+ .filter(users::id.eq(user_id))
264
+ .select(User::as_select())
265
+ .first(conn)?;
266
+
267
+ let user_posts = Post::belonging_to(&user)
268
+ .select(Post::as_select())
269
+ .load(conn)?;
270
+
271
+ Ok((user, user_posts))
272
+ }
273
+
274
+ // Load posts with authors
275
+ pub fn get_posts_with_authors(
276
+ conn: &mut PgConnection,
277
+ ) -> Result<Vec<(Post, User)>, AppError> {
278
+ posts::table
279
+ .inner_join(users::table)
280
+ .filter(posts::published.eq(true))
281
+ .order(posts::created_at.desc())
282
+ .select((Post::as_select(), User::as_select()))
283
+ .load(conn)
284
+ .map_err(|e| AppError::Database(e.to_string()))
285
+ }
286
+
287
+ // Aggregate query
288
+ pub fn get_post_stats(conn: &mut PgConnection, post_id: Uuid) -> Result<PostStats, AppError> {
289
+ use diesel::dsl::count;
290
+
291
+ let comment_count: i64 = comments::table
292
+ .filter(comments::post_id.eq(post_id))
293
+ .select(count(comments::id))
294
+ .first(conn)?;
295
+
296
+ Ok(PostStats { comment_count })
297
+ }
298
+
299
+ // Raw SQL when needed
300
+ use diesel::sql_query;
301
+ use diesel::sql_types::{Uuid as SqlUuid, BigInt};
302
+
303
+ #[derive(QueryableByName, Debug)]
304
+ pub struct UserStats {
305
+ #[diesel(sql_type = SqlUuid)]
306
+ pub user_id: Uuid,
307
+ #[diesel(sql_type = BigInt)]
308
+ pub post_count: i64,
309
+ #[diesel(sql_type = BigInt)]
310
+ pub comment_count: i64,
311
+ }
312
+
313
+ pub fn get_user_stats(conn: &mut PgConnection, user_id: Uuid) -> Result<UserStats, AppError> {
314
+ sql_query(r#"
315
+ SELECT
316
+ u.id as user_id,
317
+ COUNT(DISTINCT p.id) as post_count,
318
+ COUNT(DISTINCT c.id) as comment_count
319
+ FROM users u
320
+ LEFT JOIN posts p ON p.author_id = u.id
321
+ LEFT JOIN comments c ON c.user_id = u.id
322
+ WHERE u.id = $1
323
+ GROUP BY u.id
324
+ "#)
325
+ .bind::<SqlUuid, _>(user_id)
326
+ .get_result(conn)
327
+ .map_err(|e| AppError::Database(e.to_string()))
328
+ }
329
+ \`\`\`
330
+
331
+ ## Transactions
332
+ \`\`\`rust
333
+ use diesel::Connection;
334
+
335
+ pub fn create_user_with_profile(
336
+ conn: &mut PgConnection,
337
+ new_user: NewUser,
338
+ new_profile: NewProfile,
339
+ ) -> Result<(User, Profile), AppError> {
340
+ conn.transaction(|conn| {
341
+ let user = diesel::insert_into(users::table)
342
+ .values(&new_user)
343
+ .returning(User::as_returning())
344
+ .get_result(conn)?;
345
+
346
+ let profile = diesel::insert_into(profiles::table)
347
+ .values(&NewProfile { user_id: user.id, ..new_profile })
348
+ .returning(Profile::as_returning())
349
+ .get_result(conn)?;
350
+
351
+ Ok((user, profile))
352
+ })
353
+ .map_err(|e: diesel::result::Error| AppError::Database(e.to_string()))
354
+ }
355
+
356
+ // Nested transaction with savepoints
357
+ pub fn complex_operation(conn: &mut PgConnection) -> Result<(), AppError> {
358
+ conn.transaction(|conn| {
359
+ // First operation
360
+ create_user(conn, user1)?;
361
+
362
+ // Savepoint for nested transaction
363
+ let result = conn.transaction(|conn| {
364
+ create_user(conn, user2)
365
+ });
366
+
367
+ // Handle nested transaction failure
368
+ if let Err(e) = result {
369
+ tracing::warn!("Nested transaction failed: {:?}", e);
370
+ // Outer transaction continues
371
+ }
372
+
373
+ Ok(())
374
+ })
375
+ }
376
+ \`\`\`
377
+
378
+ ## ✅ DO
379
+ - Use \`Queryable\` and \`Selectable\` for SELECT
380
+ - Use \`Insertable\` for INSERT
381
+ - Use \`AsChangeset\` for UPDATE
382
+ - Use \`#[diesel(belongs_to(...))]\` for associations
383
+ - Use connection pools (r2d2) in production
384
+ - Use transactions for multi-step operations
385
+ - Use \`.optional()\` for queries that may return no results
386
+
387
+ ## ❌ DON'T
388
+ - Don't use \`.get_result()\` when you don't need the result
389
+ - Don't forget to run \`diesel migration run\` after schema changes
390
+ - Don't manually write schema.rs (it's auto-generated)
391
+ - Don't use \`.first()\` without \`.optional()\` unless you're sure it exists
392
+ - Don't hold connections longer than necessary
@@ -0,0 +1,301 @@
1
+ # Django Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ myproject/
6
+ ├── manage.py
7
+ ├── config/ # Project settings
8
+ │ ├── __init__.py
9
+ │ ├── settings/
10
+ │ │ ├── base.py
11
+ │ │ ├── development.py
12
+ │ │ └── production.py
13
+ │ ├── urls.py
14
+ │ └── wsgi.py
15
+ └── apps/
16
+ └── users/
17
+ ├── __init__.py
18
+ ├── admin.py
19
+ ├── apps.py
20
+ ├── models.py
21
+ ├── views.py
22
+ ├── serializers.py # DRF
23
+ ├── urls.py
24
+ ├── signals.py
25
+ ├── tasks.py # Celery
26
+ └── tests/
27
+ ├── test_models.py
28
+ └── test_views.py
29
+ \`\`\`
30
+
31
+ ## Models
32
+ \`\`\`python
33
+ from django.db import models
34
+ from django.contrib.auth.models import AbstractUser
35
+
36
+ class User(AbstractUser):
37
+ email = models.EmailField(unique=True)
38
+ bio = models.TextField(blank=True)
39
+ avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
40
+ created_at = models.DateTimeField(auto_now_add=True)
41
+ updated_at = models.DateTimeField(auto_now=True)
42
+
43
+ USERNAME_FIELD = 'email'
44
+ REQUIRED_FIELDS = ['username']
45
+
46
+ class Meta:
47
+ ordering = ['-created_at']
48
+ indexes = [
49
+ models.Index(fields=['email']),
50
+ models.Index(fields=['-created_at']),
51
+ ]
52
+
53
+ def __str__(self):
54
+ return self.email
55
+
56
+ @property
57
+ def full_name(self):
58
+ return f"{self.first_name} {self.last_name}".strip()
59
+
60
+ class Post(models.Model):
61
+ author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
62
+ title = models.CharField(max_length=200)
63
+ content = models.TextField()
64
+ published = models.BooleanField(default=False)
65
+ created_at = models.DateTimeField(auto_now_add=True)
66
+
67
+ class Meta:
68
+ ordering = ['-created_at']
69
+
70
+ # Manager for common queries
71
+ objects = models.Manager()
72
+
73
+ class PublishedManager(models.Manager):
74
+ def get_queryset(self):
75
+ return super().get_queryset().filter(published=True)
76
+
77
+ published_objects = PublishedManager()
78
+ \`\`\`
79
+
80
+ ## Django REST Framework
81
+ \`\`\`python
82
+ # serializers.py
83
+ from rest_framework import serializers
84
+
85
+ class UserSerializer(serializers.ModelSerializer):
86
+ full_name = serializers.ReadOnlyField()
87
+ posts_count = serializers.SerializerMethodField()
88
+
89
+ class Meta:
90
+ model = User
91
+ fields = ['id', 'email', 'username', 'full_name', 'posts_count', 'created_at']
92
+ read_only_fields = ['id', 'created_at']
93
+
94
+ def get_posts_count(self, obj):
95
+ return obj.posts.count()
96
+
97
+ class UserCreateSerializer(serializers.ModelSerializer):
98
+ password = serializers.CharField(write_only=True, min_length=8)
99
+
100
+ class Meta:
101
+ model = User
102
+ fields = ['email', 'username', 'password']
103
+
104
+ def create(self, validated_data):
105
+ return User.objects.create_user(**validated_data)
106
+
107
+ # views.py
108
+ from rest_framework import viewsets, permissions, status
109
+ from rest_framework.decorators import action
110
+ from rest_framework.response import Response
111
+
112
+ class UserViewSet(viewsets.ModelViewSet):
113
+ queryset = User.objects.all()
114
+ permission_classes = [permissions.IsAuthenticated]
115
+
116
+ def get_serializer_class(self):
117
+ if self.action == 'create':
118
+ return UserCreateSerializer
119
+ return UserSerializer
120
+
121
+ def get_queryset(self):
122
+ # Optimize queries
123
+ return User.objects.prefetch_related('posts').all()
124
+
125
+ @action(detail=False, methods=['get'])
126
+ def me(self, request):
127
+ serializer = self.get_serializer(request.user)
128
+ return Response(serializer.data)
129
+
130
+ @action(detail=True, methods=['post'])
131
+ def follow(self, request, pk=None):
132
+ user = self.get_object()
133
+ request.user.following.add(user)
134
+ return Response({'status': 'following'})
135
+
136
+ # urls.py
137
+ from rest_framework.routers import DefaultRouter
138
+
139
+ router = DefaultRouter()
140
+ router.register('users', UserViewSet)
141
+
142
+ urlpatterns = router.urls
143
+ \`\`\`
144
+
145
+ ## Views (Class-Based)
146
+ \`\`\`python
147
+ from django.views.generic import ListView, DetailView, CreateView
148
+ from django.contrib.auth.mixins import LoginRequiredMixin
149
+
150
+ class PostListView(ListView):
151
+ model = Post
152
+ template_name = 'posts/list.html'
153
+ context_object_name = 'posts'
154
+ paginate_by = 10
155
+
156
+ def get_queryset(self):
157
+ return Post.published_objects.select_related('author')
158
+
159
+ class PostDetailView(DetailView):
160
+ model = Post
161
+ template_name = 'posts/detail.html'
162
+
163
+ class PostCreateView(LoginRequiredMixin, CreateView):
164
+ model = Post
165
+ fields = ['title', 'content']
166
+ success_url = '/posts/'
167
+
168
+ def form_valid(self, form):
169
+ form.instance.author = self.request.user
170
+ return super().form_valid(form)
171
+ \`\`\`
172
+
173
+ ## Signals
174
+ \`\`\`python
175
+ # signals.py
176
+ from django.db.models.signals import post_save, pre_delete
177
+ from django.dispatch import receiver
178
+ from django.core.mail import send_mail
179
+
180
+ @receiver(post_save, sender=User)
181
+ def send_welcome_email(sender, instance, created, **kwargs):
182
+ if created:
183
+ send_mail(
184
+ 'Welcome!',
185
+ 'Thanks for signing up.',
186
+ 'noreply@example.com',
187
+ [instance.email],
188
+ )
189
+
190
+ @receiver(pre_delete, sender=User)
191
+ def cleanup_user_data(sender, instance, **kwargs):
192
+ # Clean up related data
193
+ instance.posts.all().delete()
194
+
195
+ # apps.py - Register signals
196
+ class UsersConfig(AppConfig):
197
+ name = 'apps.users'
198
+
199
+ def ready(self):
200
+ import apps.users.signals # noqa
201
+ \`\`\`
202
+
203
+ ## Middleware
204
+ \`\`\`python
205
+ # middleware.py
206
+ import time
207
+ import logging
208
+
209
+ logger = logging.getLogger(__name__)
210
+
211
+ class RequestTimingMiddleware:
212
+ def __init__(self, get_response):
213
+ self.get_response = get_response
214
+
215
+ def __call__(self, request):
216
+ start_time = time.time()
217
+ response = self.get_response(request)
218
+ duration = time.time() - start_time
219
+ logger.info(f"{request.method} {request.path} - {duration:.2f}s")
220
+ return response
221
+
222
+ class CurrentUserMiddleware:
223
+ def __init__(self, get_response):
224
+ self.get_response = get_response
225
+
226
+ def __call__(self, request):
227
+ # Make user available in thread-local
228
+ from threading import local
229
+ _thread_locals = local()
230
+ _thread_locals.user = request.user
231
+ return self.get_response(request)
232
+ \`\`\`
233
+
234
+ ## Admin Customization
235
+ \`\`\`python
236
+ from django.contrib import admin
237
+ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
238
+
239
+ @admin.register(User)
240
+ class UserAdmin(BaseUserAdmin):
241
+ list_display = ['email', 'username', 'is_active', 'created_at']
242
+ list_filter = ['is_active', 'is_staff', 'created_at']
243
+ search_fields = ['email', 'username']
244
+ ordering = ['-created_at']
245
+
246
+ fieldsets = BaseUserAdmin.fieldsets + (
247
+ ('Profile', {'fields': ('bio', 'avatar')}),
248
+ )
249
+
250
+ @admin.register(Post)
251
+ class PostAdmin(admin.ModelAdmin):
252
+ list_display = ['title', 'author', 'published', 'created_at']
253
+ list_filter = ['published', 'created_at']
254
+ search_fields = ['title', 'content']
255
+ raw_id_fields = ['author']
256
+ date_hierarchy = 'created_at'
257
+
258
+ actions = ['publish_posts']
259
+
260
+ @admin.action(description='Publish selected posts')
261
+ def publish_posts(self, request, queryset):
262
+ queryset.update(published=True)
263
+ \`\`\`
264
+
265
+ ## Query Optimization
266
+ \`\`\`python
267
+ # ❌ N+1 Problem
268
+ posts = Post.objects.all()
269
+ for post in posts:
270
+ print(post.author.name) # Query per post!
271
+
272
+ # ✅ select_related (ForeignKey, OneToOne)
273
+ posts = Post.objects.select_related('author').all()
274
+
275
+ # ✅ prefetch_related (ManyToMany, reverse FK)
276
+ users = User.objects.prefetch_related('posts').all()
277
+
278
+ # ✅ Only select needed fields
279
+ users = User.objects.only('id', 'email').all()
280
+ users = User.objects.values('id', 'email')
281
+
282
+ # ✅ Aggregate queries
283
+ from django.db.models import Count, Avg
284
+ User.objects.annotate(post_count=Count('posts'))
285
+ \`\`\`
286
+
287
+ ## ❌ DON'T
288
+ - Put business logic in views
289
+ - Use raw SQL without parameterization
290
+ - Forget migrations after model changes
291
+ - Skip select_related/prefetch_related
292
+ - Use synchronous code in async views
293
+
294
+ ## ✅ DO
295
+ - Fat models, thin views
296
+ - Use select_related/prefetch_related
297
+ - Use Django REST Framework for APIs
298
+ - Use signals for decoupled logic
299
+ - Customize admin for easy management
300
+ - Use class-based views for reusability
301
+ - Write tests for models and views