oh-my-customcode 0.24.2 → 0.30.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.
@@ -0,0 +1,440 @@
1
+ ---
2
+ name: django-best-practices
3
+ description: Django patterns for production-ready Python web applications
4
+ user-invocable: false
5
+ ---
6
+
7
+ ## Purpose
8
+
9
+ Apply Django patterns for building production-ready, secure, and maintainable Python web applications.
10
+
11
+ ## Rules
12
+
13
+ ### 1. Project Structure
14
+
15
+ ```yaml
16
+ structure:
17
+ settings_split: true
18
+ layout: |
19
+ project/
20
+ ├── config/
21
+ │ ├── settings/
22
+ │ │ ├── base.py
23
+ │ │ ├── development.py
24
+ │ │ └── production.py
25
+ │ ├── urls.py
26
+ │ └── wsgi.py
27
+ ├── apps/
28
+ │ ├── core/ # Shared utilities, base models
29
+ │ ├── users/ # Custom User model (ALWAYS create)
30
+ │ └── {feature}/ # Feature-specific apps
31
+ ├── templates/
32
+ ├── static/
33
+ ├── requirements/
34
+ │ ├── base.txt
35
+ │ ├── development.txt
36
+ │ └── production.txt
37
+ └── manage.py
38
+
39
+ app_module_contents:
40
+ models.py: Database models
41
+ views.py: Request handlers
42
+ urls.py: URL patterns (with app_name)
43
+ serializers.py: DRF serializers (if API)
44
+ forms.py: Django forms
45
+ admin.py: Admin customization
46
+ services.py: Business logic layer
47
+ managers.py: Custom model managers
48
+ signals.py: Django signals (use sparingly)
49
+ tests/: Test suite (mirror app structure)
50
+ ```
51
+
52
+ ### 2. Models Best Practices
53
+
54
+ ```yaml
55
+ custom_user_model:
56
+ rule: ALWAYS create a custom User model, even if identical to default
57
+ location: apps/users/models.py
58
+ reason: Impossible to swap default User model mid-project
59
+ example: |
60
+ # apps/users/models.py
61
+ from django.contrib.auth.models import AbstractUser
62
+
63
+ class User(AbstractUser):
64
+ pass # Can extend later without migrations
65
+
66
+ # config/settings/base.py
67
+ AUTH_USER_MODEL = 'users.User'
68
+
69
+ primary_key:
70
+ default: BigAutoField
71
+ settings: DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
72
+
73
+ model_meta:
74
+ required:
75
+ - __str__: human-readable representation
76
+ - Meta.ordering: consistent default ordering
77
+ - Meta.verbose_name: singular display name
78
+ - Meta.verbose_name_plural: plural display name
79
+ example: |
80
+ class Article(models.Model):
81
+ title = models.CharField(max_length=200)
82
+ created_at = models.DateTimeField(auto_now_add=True)
83
+
84
+ class Meta:
85
+ ordering = ['-created_at']
86
+ verbose_name = 'article'
87
+ verbose_name_plural = 'articles'
88
+
89
+ def __str__(self):
90
+ return self.title
91
+
92
+ query_optimization:
93
+ foreign_key: select_related() # Single SQL JOIN
94
+ many_to_many: prefetch_related() # Separate query + Python join
95
+ partial_fields: only() / defer() # Load subset of fields
96
+ aggregations: F() and Q() objects for complex expressions
97
+ bulk_ops:
98
+ create: bulk_create(objs, batch_size=1000)
99
+ update: bulk_update(objs, fields, batch_size=1000)
100
+
101
+ indexing:
102
+ - Frequently filtered fields: db_index=True
103
+ - Frequently ordered fields: Meta.ordering fields
104
+ - Multi-column: Meta.indexes with models.Index
105
+ - Unique together: Meta.unique_together or UniqueConstraint
106
+
107
+ constraints:
108
+ use: Meta.constraints for database-level enforcement
109
+ example: |
110
+ class Meta:
111
+ constraints = [
112
+ models.UniqueConstraint(
113
+ fields=['user', 'article'],
114
+ name='unique_user_article'
115
+ ),
116
+ models.CheckConstraint(
117
+ check=models.Q(price__gte=0),
118
+ name='price_non_negative'
119
+ )
120
+ ]
121
+
122
+ soft_delete:
123
+ pattern: is_active = models.BooleanField(default=True)
124
+ manager: Override default manager to filter is_active=True
125
+
126
+ custom_managers:
127
+ rule: Use managers for reusable querysets
128
+ example: |
129
+ class PublishedManager(models.Manager):
130
+ def get_queryset(self):
131
+ return super().get_queryset().filter(status='published')
132
+
133
+ class Article(models.Model):
134
+ objects = models.Manager() # Keep default
135
+ published = PublishedManager() # Add custom
136
+ ```
137
+
138
+ ### 3. Views Best Practices
139
+
140
+ ```yaml
141
+ cbv_vs_fbv:
142
+ cbv: Standard CRUD, predictable patterns (ListView, DetailView, etc.)
143
+ fbv: Complex custom logic, non-standard workflows
144
+
145
+ thin_views:
146
+ rule: Keep views thin — delegate business logic to services/models
147
+ wrong: |
148
+ def create_order(request):
149
+ # 50 lines of business logic in view
150
+ correct: |
151
+ def create_order(request):
152
+ form = OrderForm(request.POST)
153
+ if form.is_valid():
154
+ order = order_service.create(request.user, form.cleaned_data)
155
+ return redirect('order-detail', pk=order.pk)
156
+
157
+ shortcuts:
158
+ - get_object_or_404(Model, pk=pk): Returns 404 instead of 500
159
+ - get_list_or_404(Model, **kwargs): 404 if empty list
160
+
161
+ mixins:
162
+ authentication: LoginRequiredMixin
163
+ permissions: PermissionRequiredMixin
164
+ user_pass: UserPassesTestMixin
165
+
166
+ status_codes:
167
+ 200: OK (default for success)
168
+ 201: Created (after successful creation)
169
+ 302: Redirect (after POST success — PRG pattern)
170
+ 400: Bad Request (validation error)
171
+ 403: Forbidden (permission denied)
172
+ 404: Not Found
173
+ 405: Method Not Allowed
174
+ ```
175
+
176
+ ### 4. URL Patterns
177
+
178
+ ```yaml
179
+ namespacing:
180
+ app_name: Required in every app's urls.py
181
+ usage: reverse('app_name:url_name') or {% url 'app_name:url_name' %}
182
+
183
+ syntax:
184
+ prefer: path() over re_path() for clarity
185
+ use_re_path: Only for complex regex patterns
186
+
187
+ naming:
188
+ rule: Name ALL URL patterns
189
+ convention: "{resource}-{action}" (e.g., article-list, article-detail)
190
+
191
+ inclusion:
192
+ root_urls: Use include() for app-level URLs
193
+ example: |
194
+ # config/urls.py
195
+ urlpatterns = [
196
+ path('admin/', admin.site.urls),
197
+ path('api/', include('apps.api.urls', namespace='api')),
198
+ path('articles/', include('apps.articles.urls', namespace='articles')),
199
+ ]
200
+
201
+ # apps/articles/urls.py
202
+ app_name = 'articles'
203
+ urlpatterns = [
204
+ path('', ArticleListView.as_view(), name='list'),
205
+ path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),
206
+ ]
207
+ ```
208
+
209
+ ### 5. Forms & Validation
210
+
211
+ ```yaml
212
+ model_forms:
213
+ rule: Use ModelForm when form maps to a model
214
+ fields: Explicitly list fields (never use fields = '__all__')
215
+
216
+ validation:
217
+ field_level: clean_<field>() method
218
+ cross_field: clean() method
219
+ built_in: Use Django validators (MaxValueValidator, RegexValidator, etc.)
220
+
221
+ example: |
222
+ class ArticleForm(forms.ModelForm):
223
+ class Meta:
224
+ model = Article
225
+ fields = ['title', 'body', 'status']
226
+
227
+ def clean_title(self):
228
+ title = self.cleaned_data['title']
229
+ if len(title) < 5:
230
+ raise forms.ValidationError('Title too short.')
231
+ return title
232
+
233
+ def clean(self):
234
+ cleaned = super().clean()
235
+ # Cross-field validation here
236
+ return cleaned
237
+ ```
238
+
239
+ ### 6. Security
240
+
241
+ ```yaml
242
+ environment:
243
+ SECRET_KEY: Never hardcode — read from environment variable
244
+ DEBUG: False in production (MUST)
245
+ ALLOWED_HOSTS: Explicitly set in production (MUST)
246
+
247
+ https:
248
+ SECURE_SSL_REDIRECT: true
249
+ SESSION_COOKIE_SECURE: true
250
+ CSRF_COOKIE_SECURE: true
251
+ SECURE_HSTS_SECONDS: 3600 # Start small, increase to 31536000
252
+ SECURE_HSTS_INCLUDE_SUBDOMAINS: true
253
+
254
+ clickjacking:
255
+ X_FRAME_OPTIONS: DENY
256
+
257
+ content_type:
258
+ SECURE_CONTENT_TYPE_NOSNIFF: true
259
+
260
+ csrf:
261
+ rule: Enabled by default via CsrfViewMiddleware — do NOT disable
262
+
263
+ sql_injection:
264
+ rule: Use ORM, avoid raw SQL; if needed, use parameterized queries
265
+
266
+ xss:
267
+ rule: Templates auto-escape by default — never use |safe with user content
268
+
269
+ deployment_check:
270
+ command: python manage.py check --deploy
271
+ run: Before every production deployment
272
+ ```
273
+
274
+ ### 7. Testing
275
+
276
+ ```yaml
277
+ framework:
278
+ preferred: pytest-django (over unittest)
279
+ config: pytest.ini or pyproject.toml with [tool.pytest.ini_options]
280
+
281
+ test_classes:
282
+ database: TestCase (wraps each test in transaction)
283
+ no_database: SimpleTestCase (faster)
284
+ live_server: LiveServerTestCase (for Selenium)
285
+
286
+ test_data:
287
+ preferred: factory_boy or model_bakery
288
+ avoid: fixtures (hard to maintain, slow)
289
+ example: |
290
+ import factory
291
+ from apps.users.models import User
292
+
293
+ class UserFactory(factory.django.DjangoModelFactory):
294
+ class Meta:
295
+ model = User
296
+ username = factory.Sequence(lambda n: f'user{n}')
297
+ email = factory.LazyAttribute(lambda o: f'{o.username}@example.com')
298
+
299
+ request_testing:
300
+ Client: Full request/response cycle (preferred for views)
301
+ RequestFactory: Faster, no middleware (for unit testing views)
302
+
303
+ settings_override:
304
+ decorator: '@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")'
305
+
306
+ coverage:
307
+ target: 80%+
308
+ exclude: migrations, admin, settings
309
+
310
+ structure:
311
+ mirror_app: tests/test_models.py, tests/test_views.py, tests/test_forms.py
312
+ ```
313
+
314
+ ### 8. Performance
315
+
316
+ ```yaml
317
+ n_plus_1_prevention:
318
+ check: Use django-debug-toolbar in development
319
+ fix_fk: select_related('author', 'category')
320
+ fix_m2m: prefetch_related('tags', 'comments')
321
+ complex: Prefetch object with custom queryset
322
+
323
+ partial_loading:
324
+ only: only('id', 'title', 'created_at') # Load only these fields
325
+ defer: defer('body', 'metadata') # Load all except these
326
+ values: values('id', 'title') # Returns dicts (no ORM overhead)
327
+ values_list: values_list('id', flat=True) # Returns flat list
328
+
329
+ caching:
330
+ backend: Redis (preferred), Memcached
331
+ view_cache: '@cache_page(60 * 15)' decorator
332
+ template_cache: '{% cache 500 sidebar %}' template tag
333
+ low_level: cache.get/set/delete for fine-grained control
334
+
335
+ pagination:
336
+ list_views: Always paginate large querysets
337
+ page_size: Set reasonable default (20-50 items)
338
+ drf: PageNumberPagination or CursorPagination
339
+
340
+ bulk_operations:
341
+ create: Article.objects.bulk_create(articles, batch_size=1000)
342
+ update: Article.objects.bulk_update(articles, ['status'], batch_size=1000)
343
+ avoid: Loops calling .save() on many objects
344
+ ```
345
+
346
+ ### 9. Django REST Framework (DRF)
347
+
348
+ ```yaml
349
+ serializers:
350
+ standard_crud: ModelSerializer
351
+ read_only: Use SerializerMethodField for computed values
352
+ write_validation: validate_<field>() and validate() methods
353
+ example: |
354
+ class ArticleSerializer(serializers.ModelSerializer):
355
+ author_name = serializers.SerializerMethodField()
356
+
357
+ class Meta:
358
+ model = Article
359
+ fields = ['id', 'title', 'body', 'author_name', 'created_at']
360
+ read_only_fields = ['id', 'created_at']
361
+
362
+ def get_author_name(self, obj):
363
+ return obj.author.get_full_name()
364
+
365
+ viewsets:
366
+ standard: ModelViewSet for full CRUD
367
+ custom: ViewSet with explicit action methods
368
+ routers: DefaultRouter or SimpleRouter for URL generation
369
+
370
+ authentication:
371
+ jwt: djangorestframework-simplejwt (recommended)
372
+ token: DRF built-in TokenAuthentication
373
+ session: SessionAuthentication (for browser clients)
374
+
375
+ permissions:
376
+ global: DEFAULT_PERMISSION_CLASSES in settings
377
+ per_view: permission_classes attribute on ViewSet
378
+ object_level: has_object_permission() in custom permission class
379
+
380
+ versioning:
381
+ method: NamespaceVersioning or URLPathVersioning
382
+ example: "/api/v1/articles/" vs "/api/v2/articles/"
383
+
384
+ throttling:
385
+ anonymous: AnonRateThrottle
386
+ authenticated: UserRateThrottle
387
+ custom: Extend BaseThrottle
388
+
389
+ pagination:
390
+ global: DEFAULT_PAGINATION_CLASS in settings
391
+ types: PageNumberPagination (simple), CursorPagination (large datasets)
392
+ ```
393
+
394
+ ### 10. Deployment
395
+
396
+ ```yaml
397
+ wsgi_asgi:
398
+ wsgi: gunicorn (4 workers per CPU core)
399
+ asgi: uvicorn with gunicorn workers (for async/WebSocket)
400
+
401
+ static_files:
402
+ development: Django's staticfiles
403
+ production: whitenoise middleware OR CDN (S3 + CloudFront)
404
+ command: python manage.py collectstatic --noinput
405
+
406
+ database:
407
+ development: SQLite (acceptable)
408
+ production: PostgreSQL (MUST — never SQLite in production)
409
+ connection_pooling: Use pgBouncer or django-db-connection-pool
410
+
411
+ migrations:
412
+ deploy: Run as part of CI/CD pipeline before server restart
413
+ zero_downtime: Use additive migrations (add nullable columns, backfill, then add constraint)
414
+
415
+ logging:
416
+ config: LOGGING dict in settings
417
+ handler: File handler in production, console in development
418
+ level: WARNING in production, DEBUG in development
419
+
420
+ environment_variables:
421
+ tool: python-decouple or django-environ
422
+ never: Hardcode secrets in settings files
423
+
424
+ health_check:
425
+ endpoint: /health/ returning 200 OK
426
+ checks: Database connection, cache connection, disk space
427
+ ```
428
+
429
+ ## Application
430
+
431
+ When writing Django code:
432
+
433
+ 1. **Always** create a custom User model before any other models
434
+ 2. **Always** split settings into base/development/production
435
+ 3. **Prefer** CBVs for standard CRUD, FBVs for custom logic
436
+ 4. **Use** select_related/prefetch_related to prevent N+1 queries
437
+ 5. **Apply** the security checklist for every production deployment
438
+ 6. **Test** with pytest-django and factory_boy
439
+ 7. **Never** use `fields = '__all__'` in ModelForms or ModelSerializer
440
+ 8. **Run** `python manage.py check --deploy` before shipping
@@ -174,12 +174,12 @@ Violation = immediate correction. No exception for "small changes".
174
174
  project/
175
175
  +-- CLAUDE.md # Entry point
176
176
  +-- .claude/
177
- | +-- agents/ # Subagent definitions (41 files)
178
- | +-- skills/ # Skills (56 directories)
177
+ | +-- agents/ # Subagent definitions (43 files)
178
+ | +-- skills/ # Skills (67 directories)
179
179
  | +-- rules/ # Global rules (R000-R018)
180
180
  | +-- hooks/ # Hook scripts (memory, HUD)
181
181
  | +-- contexts/ # Context files (ecomode)
182
- +-- guides/ # Reference docs (22 topics)
182
+ +-- guides/ # Reference docs (23 topics)
183
183
  ```
184
184
 
185
185
  ## Orchestration
@@ -208,7 +208,7 @@ This is the core oh-my-customcode philosophy: **"No expert? CREATE one, connect
208
208
  | Type | Count | Agents |
209
209
  |------|-------|--------|
210
210
  | SW Engineer/Language | 6 | lang-golang-expert, lang-python-expert, lang-rust-expert, lang-kotlin-expert, lang-typescript-expert, lang-java21-expert |
211
- | SW Engineer/Backend | 5 | be-fastapi-expert, be-springboot-expert, be-go-backend-expert, be-express-expert, be-nestjs-expert |
211
+ | SW Engineer/Backend | 6 | be-fastapi-expert, be-springboot-expert, be-go-backend-expert, be-express-expert, be-nestjs-expert, be-django-expert |
212
212
  | SW Engineer/Frontend | 3 | fe-vercel-agent, fe-vuejs-agent, fe-svelte-agent |
213
213
  | SW Engineer/Tooling | 3 | tool-npm-expert, tool-optimizer, tool-bun-expert |
214
214
  | DE Engineer | 6 | de-airflow-expert, de-dbt-expert, de-spark-expert, de-kafka-expert, de-snowflake-expert, de-pipeline-expert |
@@ -218,7 +218,7 @@ This is the core oh-my-customcode philosophy: **"No expert? CREATE one, connect
218
218
  | QA Team | 3 | qa-planner, qa-writer, qa-engineer |
219
219
  | Manager | 6 | mgr-creator, mgr-updater, mgr-supplier, mgr-gitnerd, mgr-sauron, mgr-claude-code-bible |
220
220
  | System | 2 | sys-memory-keeper, sys-naggy |
221
- | **Total** | **41** | |
221
+ | **Total** | **42** | |
222
222
 
223
223
  ## Agent Teams (MUST when enabled)
224
224
 
@@ -174,12 +174,12 @@ oh-my-customcode로 구동됩니다.
174
174
  project/
175
175
  +-- CLAUDE.md # 진입점
176
176
  +-- .claude/
177
- | +-- agents/ # 서브에이전트 정의 (41 파일)
178
- | +-- skills/ # 스킬 (56 디렉토리)
177
+ | +-- agents/ # 서브에이전트 정의 (43 파일)
178
+ | +-- skills/ # 스킬 (67 디렉토리)
179
179
  | +-- rules/ # 전역 규칙 (R000-R018)
180
180
  | +-- hooks/ # 훅 스크립트 (메모리, HUD)
181
181
  | +-- contexts/ # 컨텍스트 파일 (ecomode)
182
- +-- guides/ # 레퍼런스 문서 (22 토픽)
182
+ +-- guides/ # 레퍼런스 문서 (23 토픽)
183
183
  ```
184
184
 
185
185
  ## 오케스트레이션
@@ -208,7 +208,7 @@ project/
208
208
  | 타입 | 수량 | 에이전트 |
209
209
  |------|------|----------|
210
210
  | SW Engineer/Language | 6 | lang-golang-expert, lang-python-expert, lang-rust-expert, lang-kotlin-expert, lang-typescript-expert, lang-java21-expert |
211
- | SW Engineer/Backend | 5 | be-fastapi-expert, be-springboot-expert, be-go-backend-expert, be-express-expert, be-nestjs-expert |
211
+ | SW Engineer/Backend | 6 | be-fastapi-expert, be-springboot-expert, be-go-backend-expert, be-express-expert, be-nestjs-expert, be-django-expert |
212
212
  | SW Engineer/Frontend | 3 | fe-vercel-agent, fe-vuejs-agent, fe-svelte-agent |
213
213
  | SW Engineer/Tooling | 3 | tool-npm-expert, tool-optimizer, tool-bun-expert |
214
214
  | DE Engineer | 6 | de-airflow-expert, de-dbt-expert, de-spark-expert, de-kafka-expert, de-snowflake-expert, de-pipeline-expert |
@@ -218,7 +218,7 @@ project/
218
218
  | QA Team | 3 | qa-planner, qa-writer, qa-engineer |
219
219
  | Manager | 6 | mgr-creator, mgr-updater, mgr-supplier, mgr-gitnerd, mgr-sauron, mgr-claude-code-bible |
220
220
  | System | 2 | sys-memory-keeper, sys-naggy |
221
- | **총계** | **41** | |
221
+ | **총계** | **42** | |
222
222
 
223
223
  ## Agent Teams (MUST when enabled)
224
224