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,476 @@
1
+ # Django Best Practices Guide
2
+
3
+ > Reference: Django 6.0 Official Documentation + Community Best Practices
4
+
5
+ ## Sources
6
+
7
+ - https://docs.djangoproject.com/en/6.0/
8
+ - https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
9
+ - https://github.com/HackSoftware/Django-Styleguide (HackSoft style guide)
10
+
11
+ ---
12
+
13
+ ## Quick Reference
14
+
15
+ ### Project Setup
16
+
17
+ **Recommended project structure:**
18
+
19
+ ```
20
+ project/
21
+ ├── config/
22
+ │ ├── settings/
23
+ │ │ ├── base.py # Shared settings
24
+ │ │ ├── development.py # Dev overrides
25
+ │ │ └── production.py # Prod overrides
26
+ │ ├── urls.py
27
+ │ └── wsgi.py
28
+ ├── apps/
29
+ │ ├── core/ # Shared utilities
30
+ │ ├── users/ # Custom User model
31
+ │ └── {feature}/ # Feature apps
32
+ ├── templates/
33
+ ├── static/
34
+ ├── requirements/
35
+ │ ├── base.txt
36
+ │ ├── development.txt # + debug-toolbar, factory-boy
37
+ │ └── production.txt # + gunicorn, whitenoise
38
+ └── manage.py
39
+ ```
40
+
41
+ **Settings split pattern:**
42
+
43
+ ```python
44
+ # config/settings/base.py
45
+ SECRET_KEY = env('SECRET_KEY')
46
+ DEBUG = False
47
+ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
48
+ AUTH_USER_MODEL = 'users.User'
49
+
50
+ # config/settings/development.py
51
+ from .base import *
52
+ DEBUG = True
53
+ INSTALLED_APPS += ['debug_toolbar']
54
+
55
+ # config/settings/production.py
56
+ from .base import *
57
+ ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
58
+ DATABASES = {'default': env.db()}
59
+ ```
60
+
61
+ **Always create a custom User model first:**
62
+
63
+ ```python
64
+ # apps/users/models.py
65
+ from django.contrib.auth.models import AbstractUser
66
+
67
+ class User(AbstractUser):
68
+ pass # Extend later without pain
69
+ ```
70
+
71
+ ---
72
+
73
+ ### Models
74
+
75
+ **Model best practices:**
76
+
77
+ ```python
78
+ from django.db import models
79
+
80
+ class Article(models.Model):
81
+ title = models.CharField(max_length=200, db_index=True)
82
+ body = models.TextField()
83
+ author = models.ForeignKey('users.User', on_delete=models.CASCADE)
84
+ status = models.CharField(
85
+ max_length=20,
86
+ choices=[('draft', 'Draft'), ('published', 'Published')],
87
+ default='draft'
88
+ )
89
+ created_at = models.DateTimeField(auto_now_add=True)
90
+ updated_at = models.DateTimeField(auto_now=True)
91
+
92
+ objects = models.Manager()
93
+ published = PublishedManager() # Custom manager
94
+
95
+ class Meta:
96
+ ordering = ['-created_at']
97
+ verbose_name = 'article'
98
+ verbose_name_plural = 'articles'
99
+ indexes = [
100
+ models.Index(fields=['status', 'created_at']),
101
+ ]
102
+ constraints = [
103
+ models.CheckConstraint(
104
+ check=~models.Q(title=''),
105
+ name='article_title_not_empty'
106
+ )
107
+ ]
108
+
109
+ def __str__(self):
110
+ return self.title
111
+ ```
112
+
113
+ **Query optimization:**
114
+
115
+ ```python
116
+ # N+1 prevention
117
+ articles = Article.objects.select_related('author').prefetch_related('tags')
118
+
119
+ # Partial field loading
120
+ titles = Article.objects.values_list('id', 'title') # No ORM object
121
+
122
+ # Bulk operations (never loop .save())
123
+ Article.objects.bulk_create(articles, batch_size=1000)
124
+ Article.objects.bulk_update(articles, ['status'], batch_size=1000)
125
+
126
+ # Complex queries with F() and Q()
127
+ from django.db.models import F, Q
128
+ Article.objects.filter(Q(status='published') | Q(author=request.user))
129
+ Article.objects.update(view_count=F('view_count') + 1)
130
+ ```
131
+
132
+ ---
133
+
134
+ ### Views & URLs
135
+
136
+ **Class-Based Views for standard CRUD:**
137
+
138
+ ```python
139
+ from django.contrib.auth.mixins import LoginRequiredMixin
140
+ from django.views.generic import ListView, DetailView, CreateView
141
+
142
+ class ArticleListView(ListView):
143
+ model = Article
144
+ template_name = 'articles/list.html'
145
+ context_object_name = 'articles'
146
+ paginate_by = 20
147
+
148
+ def get_queryset(self):
149
+ return Article.published.select_related('author')
150
+
151
+ class ArticleCreateView(LoginRequiredMixin, CreateView):
152
+ model = Article
153
+ form_class = ArticleForm
154
+ template_name = 'articles/form.html'
155
+
156
+ def form_valid(self, form):
157
+ form.instance.author = self.request.user
158
+ return super().form_valid(form)
159
+ ```
160
+
161
+ **URL namespacing (required):**
162
+
163
+ ```python
164
+ # apps/articles/urls.py
165
+ app_name = 'articles' # REQUIRED
166
+
167
+ urlpatterns = [
168
+ path('', ArticleListView.as_view(), name='list'),
169
+ path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),
170
+ path('create/', ArticleCreateView.as_view(), name='create'),
171
+ ]
172
+
173
+ # Usage: reverse('articles:detail', args=[pk])
174
+ # Template: {% url 'articles:detail' article.pk %}
175
+ ```
176
+
177
+ ---
178
+
179
+ ### Security Checklist
180
+
181
+ Run before every production deployment: `python manage.py check --deploy`
182
+
183
+ **Required production settings:**
184
+
185
+ ```python
186
+ # config/settings/production.py
187
+
188
+ # Core
189
+ DEBUG = False
190
+ SECRET_KEY = env('SECRET_KEY')
191
+ ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
192
+
193
+ # HTTPS
194
+ SECURE_SSL_REDIRECT = True
195
+ SECURE_HSTS_SECONDS = 31536000
196
+ SECURE_HSTS_INCLUDE_SUBDOMAINS = True
197
+ SECURE_HSTS_PRELOAD = True
198
+
199
+ # Cookies
200
+ SESSION_COOKIE_SECURE = True
201
+ CSRF_COOKIE_SECURE = True
202
+ SESSION_COOKIE_HTTPONLY = True
203
+ CSRF_COOKIE_HTTPONLY = True
204
+
205
+ # Content security
206
+ SECURE_CONTENT_TYPE_NOSNIFF = True
207
+ X_FRAME_OPTIONS = 'DENY'
208
+ SECURE_BROWSER_XSS_FILTER = True
209
+ ```
210
+
211
+ **Security built-ins (never disable):**
212
+
213
+ | Protection | Middleware / Setting | Default |
214
+ |------------|---------------------|---------|
215
+ | CSRF | `CsrfViewMiddleware` | On |
216
+ | XSS | Template auto-escaping | On |
217
+ | SQL injection | ORM parameterized queries | On |
218
+ | Clickjacking | `XFrameOptionsMiddleware` | On |
219
+ | Session security | `SessionMiddleware` | On |
220
+
221
+ ---
222
+
223
+ ### Performance
224
+
225
+ **N+1 query prevention:**
226
+
227
+ ```python
228
+ # Bad: N+1
229
+ for article in Article.objects.all():
230
+ print(article.author.username) # 1 query per article
231
+
232
+ # Good: 2 queries total
233
+ for article in Article.objects.select_related('author'):
234
+ print(article.author.username)
235
+
236
+ # Good: M2M prefetch
237
+ Article.objects.prefetch_related('tags', 'comments__author')
238
+
239
+ # Advanced: Custom prefetch
240
+ from django.db.models import Prefetch
241
+ Article.objects.prefetch_related(
242
+ Prefetch('comments', queryset=Comment.objects.filter(approved=True))
243
+ )
244
+ ```
245
+
246
+ **Caching:**
247
+
248
+ ```python
249
+ # Settings
250
+ CACHES = {
251
+ 'default': {
252
+ 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
253
+ 'LOCATION': env('REDIS_URL'),
254
+ }
255
+ }
256
+
257
+ # View-level caching
258
+ from django.views.decorators.cache import cache_page
259
+
260
+ @cache_page(60 * 15) # 15 minutes
261
+ def my_view(request):
262
+ ...
263
+
264
+ # Low-level API
265
+ from django.core.cache import cache
266
+ data = cache.get_or_set('my_key', expensive_function, timeout=300)
267
+ ```
268
+
269
+ ---
270
+
271
+ ### Testing
272
+
273
+ **pytest-django setup:**
274
+
275
+ ```ini
276
+ # pytest.ini
277
+ [pytest]
278
+ DJANGO_SETTINGS_MODULE = config.settings.test
279
+ python_files = tests/test_*.py
280
+ python_classes = Test*
281
+ python_functions = test_*
282
+ ```
283
+
284
+ **Factory pattern:**
285
+
286
+ ```python
287
+ # apps/articles/tests/factories.py
288
+ import factory
289
+ from factory.django import DjangoModelFactory
290
+
291
+ class UserFactory(DjangoModelFactory):
292
+ class Meta:
293
+ model = 'users.User'
294
+ username = factory.Sequence(lambda n: f'user{n}')
295
+ email = factory.LazyAttribute(lambda o: f'{o.username}@example.com')
296
+
297
+ class ArticleFactory(DjangoModelFactory):
298
+ class Meta:
299
+ model = 'articles.Article'
300
+ title = factory.Faker('sentence')
301
+ author = factory.SubFactory(UserFactory)
302
+ status = 'published'
303
+ ```
304
+
305
+ **Test structure:**
306
+
307
+ ```python
308
+ # apps/articles/tests/test_views.py
309
+ import pytest
310
+ from django.urls import reverse
311
+
312
+ @pytest.mark.django_db
313
+ class TestArticleListView:
314
+ def test_returns_published_articles(self, client):
315
+ ArticleFactory.create_batch(3, status='published')
316
+ ArticleFactory(status='draft')
317
+
318
+ url = reverse('articles:list')
319
+ response = client.get(url)
320
+
321
+ assert response.status_code == 200
322
+ assert len(response.context['articles']) == 3
323
+
324
+ def test_requires_login_for_create(self, client):
325
+ url = reverse('articles:create')
326
+ response = client.get(url)
327
+ assert response.status_code == 302 # Redirect to login
328
+ ```
329
+
330
+ ---
331
+
332
+ ### REST API (DRF)
333
+
334
+ **Serializers:**
335
+
336
+ ```python
337
+ from rest_framework import serializers
338
+
339
+ class ArticleSerializer(serializers.ModelSerializer):
340
+ author_name = serializers.SerializerMethodField()
341
+ tags = serializers.StringRelatedField(many=True)
342
+
343
+ class Meta:
344
+ model = Article
345
+ fields = ['id', 'title', 'body', 'author_name', 'tags', 'created_at']
346
+ read_only_fields = ['id', 'created_at']
347
+
348
+ def get_author_name(self, obj):
349
+ return obj.author.get_full_name()
350
+ ```
351
+
352
+ **ViewSets + Routers:**
353
+
354
+ ```python
355
+ from rest_framework import viewsets, permissions
356
+ from rest_framework.decorators import action
357
+ from rest_framework.response import Response
358
+
359
+ class ArticleViewSet(viewsets.ModelViewSet):
360
+ queryset = Article.published.select_related('author')
361
+ serializer_class = ArticleSerializer
362
+ permission_classes = [permissions.IsAuthenticatedOrReadOnly]
363
+
364
+ def perform_create(self, serializer):
365
+ serializer.save(author=self.request.user)
366
+
367
+ @action(detail=True, methods=['post'])
368
+ def publish(self, request, pk=None):
369
+ article = self.get_object()
370
+ article.status = 'published'
371
+ article.save()
372
+ return Response({'status': 'published'})
373
+
374
+ # urls.py
375
+ from rest_framework.routers import DefaultRouter
376
+
377
+ router = DefaultRouter()
378
+ router.register('articles', ArticleViewSet, basename='article')
379
+
380
+ urlpatterns = [path('api/v1/', include(router.urls))]
381
+ ```
382
+
383
+ **Authentication (JWT):**
384
+
385
+ ```python
386
+ # settings/base.py
387
+ REST_FRAMEWORK = {
388
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
389
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
390
+ ],
391
+ 'DEFAULT_PERMISSION_CLASSES': [
392
+ 'rest_framework.permissions.IsAuthenticated',
393
+ ],
394
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
395
+ 'PAGE_SIZE': 20,
396
+ 'DEFAULT_THROTTLE_CLASSES': [
397
+ 'rest_framework.throttling.AnonRateThrottle',
398
+ 'rest_framework.throttling.UserRateThrottle',
399
+ ],
400
+ 'DEFAULT_THROTTLE_RATES': {
401
+ 'anon': '100/hour',
402
+ 'user': '1000/hour',
403
+ },
404
+ }
405
+ ```
406
+
407
+ ---
408
+
409
+ ### Deployment
410
+
411
+ **Gunicorn configuration:**
412
+
413
+ ```bash
414
+ # gunicorn.conf.py
415
+ workers = multiprocessing.cpu_count() * 2 + 1
416
+ worker_class = 'sync' # or 'uvicorn.workers.UvicornWorker' for ASGI
417
+ bind = '0.0.0.0:8000'
418
+ timeout = 30
419
+ keepalive = 2
420
+ max_requests = 1000
421
+ max_requests_jitter = 100
422
+ ```
423
+
424
+ **Static files with whitenoise:**
425
+
426
+ ```python
427
+ # settings/production.py
428
+ MIDDLEWARE = [
429
+ 'django.middleware.security.SecurityMiddleware',
430
+ 'whitenoise.middleware.WhiteNoiseMiddleware', # After SecurityMiddleware
431
+ ...
432
+ ]
433
+ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
434
+ ```
435
+
436
+ **Deployment checklist:**
437
+
438
+ ```bash
439
+ # Before every production deployment
440
+ python manage.py check --deploy
441
+ python manage.py migrate --run-syncdb
442
+ python manage.py collectstatic --noinput
443
+
444
+ # Database
445
+ # Use PostgreSQL (psycopg2-binary or psycopg[binary])
446
+ # Set up pgBouncer for connection pooling
447
+
448
+ # Logging
449
+ LOGGING = {
450
+ 'version': 1,
451
+ 'disable_existing_loggers': False,
452
+ 'handlers': {
453
+ 'console': {'class': 'logging.StreamHandler'},
454
+ 'file': {'class': 'logging.FileHandler', 'filename': '/var/log/django.log'},
455
+ },
456
+ 'root': {'handlers': ['console', 'file'], 'level': 'WARNING'},
457
+ }
458
+ ```
459
+
460
+ ---
461
+
462
+ ## Package Recommendations
463
+
464
+ | Category | Package | Notes |
465
+ |----------|---------|-------|
466
+ | Settings | `django-environ` or `python-decouple` | Environment variable management |
467
+ | Auth | `djangorestframework-simplejwt` | JWT for APIs |
468
+ | API | `djangorestframework` | REST framework |
469
+ | Testing | `pytest-django`, `factory_boy` | Test infrastructure |
470
+ | Debug | `django-debug-toolbar` | Query inspection (dev only) |
471
+ | Static | `whitenoise` | Static file serving |
472
+ | Tasks | `celery` + `redis` | Background task queue |
473
+ | Caching | `django-redis` | Redis cache backend |
474
+ | Storage | `django-storages` + `boto3` | S3 media storage |
475
+ | Filtering | `django-filter` | DRF filter integration |
476
+ | Cors | `django-cors-headers` | CORS for SPA frontends |
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.24.2",
2
+ "version": "0.30.1",
3
3
  "lastUpdated": "2026-03-09T00:00:00.000Z",
4
4
  "components": [
5
5
  {
@@ -12,19 +12,19 @@
12
12
  "name": "agents",
13
13
  "path": ".claude/agents",
14
14
  "description": "AI agent definitions (flat .md files with prefixes)",
15
- "files": 42
15
+ "files": 43
16
16
  },
17
17
  {
18
18
  "name": "skills",
19
19
  "path": ".claude/skills",
20
20
  "description": "Reusable skill modules (includes slash commands)",
21
- "files": 66
21
+ "files": 67
22
22
  },
23
23
  {
24
24
  "name": "guides",
25
25
  "path": "guides",
26
26
  "description": "Reference documentation",
27
- "files": 22
27
+ "files": 23
28
28
  },
29
29
  {
30
30
  "name": "hooks",