omgkit 2.2.0 → 2.3.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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,947 +1,181 @@
1
1
  ---
2
- name: django
3
- description: Enterprise Django development with DRF, ORM optimization, async views, and production patterns
4
- category: frameworks
5
- triggers:
6
- - django
7
- - django rest framework
8
- - drf
9
- - django orm
10
- - django admin
11
- - django templates
12
- - django views
13
- - python web framework
2
+ name: building-django-apps
3
+ description: Builds enterprise Django applications with DRF, ORM optimization, async views, and Celery tasks. Use when creating Python web apps, REST APIs, or full-stack Django projects.
14
4
  ---
15
5
 
16
6
  # Django
17
7
 
18
- Enterprise-grade **Django development** following industry best practices. This skill covers Django REST Framework, ORM optimization, async views, authentication, testing patterns, and production deployment configurations used by top engineering teams.
8
+ ## Quick Start
19
9
 
20
- ## Purpose
10
+ ```python
11
+ # views.py
12
+ from rest_framework import viewsets
13
+ from rest_framework.decorators import api_view
14
+ from rest_framework.response import Response
21
15
 
22
- Build scalable Python web applications with confidence:
16
+ @api_view(['GET'])
17
+ def health_check(request):
18
+ return Response({'status': 'ok'})
23
19
 
24
- - Design clean model architectures with proper relationships
25
- - Implement REST APIs with Django REST Framework
26
- - Optimize database queries for performance
27
- - Handle authentication and permissions securely
28
- - Write comprehensive tests for reliability
29
- - Deploy production-ready applications
30
- - Leverage async views for high concurrency
20
+ class UserViewSet(viewsets.ModelViewSet):
21
+ queryset = User.objects.all()
22
+ serializer_class = UserSerializer
23
+ ```
31
24
 
32
25
  ## Features
33
26
 
34
- ### 1. Model Design and Relationships
27
+ | Feature | Description | Guide |
28
+ |---------|-------------|-------|
29
+ | Models | ORM, relationships, managers | [MODELS.md](MODELS.md) |
30
+ | Views | ViewSets, APIView, async views | [VIEWS.md](VIEWS.md) |
31
+ | Serializers | Validation, nested data | [SERIALIZERS.md](SERIALIZERS.md) |
32
+ | Auth | JWT, permissions, policies | [AUTH.md](AUTH.md) |
33
+ | Queries | select_related, prefetch, N+1 | [QUERIES.md](QUERIES.md) |
34
+ | Testing | pytest-django, fixtures | [TESTING.md](TESTING.md) |
35
+
36
+ ## Common Patterns
37
+
38
+ ### Model with Relationships
35
39
 
36
40
  ```python
37
41
  from django.db import models
38
- from django.contrib.auth.models import AbstractUser
39
- from django.utils import timezone
40
42
  from uuid import uuid4
41
43
 
42
44
  class User(AbstractUser):
43
- """Custom user model with UUID primary key and additional fields."""
44
45
  id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
45
46
  email = models.EmailField(unique=True)
46
- phone = models.CharField(max_length=20, blank=True)
47
- avatar = models.ImageField(upload_to='avatars/', blank=True)
48
- is_verified = models.BooleanField(default=False)
49
- created_at = models.DateTimeField(auto_now_add=True)
50
- updated_at = models.DateTimeField(auto_now=True)
51
47
 
52
48
  USERNAME_FIELD = 'email'
53
- REQUIRED_FIELDS = ['username']
54
49
 
55
50
  class Meta:
56
51
  db_table = 'users'
57
- ordering = ['-created_at']
58
- indexes = [
59
- models.Index(fields=['email']),
60
- models.Index(fields=['created_at']),
61
- ]
62
-
63
- def __str__(self):
64
- return self.email
65
-
52
+ indexes = [models.Index(fields=['email'])]
66
53
 
67
54
  class Organization(models.Model):
68
- """Organization with membership relationships."""
69
55
  id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
70
56
  name = models.CharField(max_length=255)
71
57
  slug = models.SlugField(unique=True)
72
- owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='owned_organizations')
58
+ owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='owned_orgs')
73
59
  members = models.ManyToManyField(User, through='Membership', related_name='organizations')
74
- created_at = models.DateTimeField(auto_now_add=True)
75
-
76
- class Meta:
77
- db_table = 'organizations'
78
-
79
-
80
- class Membership(models.Model):
81
- """Through model for organization membership with roles."""
82
- class Role(models.TextChoices):
83
- OWNER = 'owner', 'Owner'
84
- ADMIN = 'admin', 'Admin'
85
- MEMBER = 'member', 'Member'
86
- VIEWER = 'viewer', 'Viewer'
87
-
88
- user = models.ForeignKey(User, on_delete=models.CASCADE)
89
- organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
90
- role = models.CharField(max_length=20, choices=Role.choices, default=Role.MEMBER)
91
- joined_at = models.DateTimeField(auto_now_add=True)
92
-
93
- class Meta:
94
- db_table = 'memberships'
95
- unique_together = ['user', 'organization']
96
-
97
-
98
- class Project(models.Model):
99
- """Project with soft delete and audit fields."""
100
- class Status(models.TextChoices):
101
- DRAFT = 'draft', 'Draft'
102
- ACTIVE = 'active', 'Active'
103
- ARCHIVED = 'archived', 'Archived'
104
-
105
- id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
106
- organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name='projects')
107
- name = models.CharField(max_length=255)
108
- description = models.TextField(blank=True)
109
- status = models.CharField(max_length=20, choices=Status.choices, default=Status.DRAFT)
110
- created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
111
- created_at = models.DateTimeField(auto_now_add=True)
112
- updated_at = models.DateTimeField(auto_now=True)
113
- deleted_at = models.DateTimeField(null=True, blank=True)
114
-
115
- class Meta:
116
- db_table = 'projects'
117
- ordering = ['-created_at']
118
-
119
- @property
120
- def is_deleted(self):
121
- return self.deleted_at is not None
122
-
123
- def soft_delete(self):
124
- self.deleted_at = timezone.now()
125
- self.save(update_fields=['deleted_at'])
126
60
  ```
127
61
 
128
- ### 2. Django REST Framework Serializers
62
+ ### DRF Serializers
129
63
 
130
64
  ```python
131
65
  from rest_framework import serializers
132
- from django.contrib.auth import get_user_model
133
- from django.contrib.auth.password_validation import validate_password
134
-
135
- User = get_user_model()
136
-
137
66
 
138
67
  class UserSerializer(serializers.ModelSerializer):
139
- """User serializer with computed fields."""
140
68
  full_name = serializers.SerializerMethodField()
141
- organization_count = serializers.SerializerMethodField()
142
69
 
143
70
  class Meta:
144
71
  model = User
145
- fields = ['id', 'email', 'username', 'full_name', 'avatar',
146
- 'is_verified', 'organization_count', 'created_at']
147
- read_only_fields = ['id', 'is_verified', 'created_at']
72
+ fields = ['id', 'email', 'full_name', 'created_at']
73
+ read_only_fields = ['id', 'created_at']
148
74
 
149
75
  def get_full_name(self, obj):
150
- return f"{obj.first_name} {obj.last_name}".strip() or obj.username
151
-
152
- def get_organization_count(self, obj):
153
- return obj.organizations.count()
154
-
76
+ return f"{obj.first_name} {obj.last_name}".strip()
155
77
 
156
78
  class UserCreateSerializer(serializers.ModelSerializer):
157
- """User registration serializer with password validation."""
158
- password = serializers.CharField(write_only=True, validators=[validate_password])
79
+ password = serializers.CharField(write_only=True, min_length=8)
159
80
  password_confirm = serializers.CharField(write_only=True)
160
81
 
161
82
  class Meta:
162
83
  model = User
163
- fields = ['email', 'username', 'password', 'password_confirm']
84
+ fields = ['email', 'password', 'password_confirm']
164
85
 
165
86
  def validate(self, attrs):
166
87
  if attrs['password'] != attrs['password_confirm']:
167
- raise serializers.ValidationError({'password_confirm': 'Passwords do not match'})
88
+ raise serializers.ValidationError({'password_confirm': 'Passwords must match'})
168
89
  return attrs
169
90
 
170
91
  def create(self, validated_data):
171
92
  validated_data.pop('password_confirm')
172
- password = validated_data.pop('password')
173
- user = User(**validated_data)
174
- user.set_password(password)
175
- user.save()
176
- return user
177
-
178
-
179
- class OrganizationSerializer(serializers.ModelSerializer):
180
- """Organization serializer with nested relationships."""
181
- owner = UserSerializer(read_only=True)
182
- member_count = serializers.SerializerMethodField()
183
-
184
- class Meta:
185
- model = Organization
186
- fields = ['id', 'name', 'slug', 'owner', 'member_count', 'created_at']
187
- read_only_fields = ['id', 'owner', 'created_at']
188
-
189
- def get_member_count(self, obj):
190
- return obj.members.count()
191
-
192
- def create(self, validated_data):
193
- user = self.context['request'].user
194
- org = Organization.objects.create(owner=user, **validated_data)
195
- Membership.objects.create(user=user, organization=org, role=Membership.Role.OWNER)
196
- return org
197
-
198
-
199
- class ProjectSerializer(serializers.ModelSerializer):
200
- """Project serializer with validation."""
201
- created_by = UserSerializer(read_only=True)
202
-
203
- class Meta:
204
- model = Project
205
- fields = ['id', 'name', 'description', 'status', 'created_by',
206
- 'created_at', 'updated_at']
207
- read_only_fields = ['id', 'created_by', 'created_at', 'updated_at']
208
-
209
- def validate_name(self, value):
210
- org = self.context.get('organization')
211
- if org and Project.objects.filter(organization=org, name=value).exists():
212
- raise serializers.ValidationError('Project with this name already exists')
213
- return value
93
+ return User.objects.create_user(**validated_data)
214
94
  ```
215
95
 
216
- ### 3. Views and ViewSets
96
+ ### ViewSet with Permissions
217
97
 
218
98
  ```python
219
- from rest_framework import viewsets, status, permissions
99
+ from rest_framework import viewsets, permissions
220
100
  from rest_framework.decorators import action
221
101
  from rest_framework.response import Response
222
- from rest_framework.views import APIView
223
- from django.shortcuts import get_object_or_404
224
- from django.db.models import Q, Count, Prefetch
225
-
226
102
 
227
103
  class UserViewSet(viewsets.ModelViewSet):
228
- """User management viewset with custom actions."""
229
- queryset = User.objects.all()
230
104
  permission_classes = [permissions.IsAuthenticated]
231
105
 
106
+ def get_queryset(self):
107
+ return User.objects.select_related('profile').prefetch_related('organizations')
108
+
232
109
  def get_serializer_class(self):
233
110
  if self.action == 'create':
234
111
  return UserCreateSerializer
235
112
  return UserSerializer
236
113
 
237
- def get_queryset(self):
238
- queryset = super().get_queryset()
239
- # Optimize with prefetch
240
- return queryset.prefetch_related(
241
- Prefetch('organizations', queryset=Organization.objects.only('id', 'name'))
242
- )
243
-
244
114
  @action(detail=False, methods=['get', 'patch'])
245
115
  def me(self, request):
246
- """Get or update current user."""
247
116
  if request.method == 'GET':
248
- serializer = self.get_serializer(request.user)
249
- return Response(serializer.data)
250
-
251
- serializer = self.get_serializer(request.user, data=request.data, partial=True)
117
+ return Response(UserSerializer(request.user).data)
118
+ serializer = UserSerializer(request.user, data=request.data, partial=True)
252
119
  serializer.is_valid(raise_exception=True)
253
120
  serializer.save()
254
121
  return Response(serializer.data)
255
-
256
- @action(detail=True, methods=['post'])
257
- def verify(self, request, pk=None):
258
- """Admin action to verify a user."""
259
- if not request.user.is_staff:
260
- return Response({'error': 'Admin only'}, status=status.HTTP_403_FORBIDDEN)
261
-
262
- user = self.get_object()
263
- user.is_verified = True
264
- user.save(update_fields=['is_verified'])
265
- return Response({'status': 'verified'})
266
-
267
-
268
- class OrganizationViewSet(viewsets.ModelViewSet):
269
- """Organization viewset with membership management."""
270
- serializer_class = OrganizationSerializer
271
- permission_classes = [permissions.IsAuthenticated]
272
- lookup_field = 'slug'
273
-
274
- def get_queryset(self):
275
- return Organization.objects.filter(
276
- members=self.request.user
277
- ).select_related('owner').annotate(
278
- member_count=Count('members')
279
- )
280
-
281
- @action(detail=True, methods=['get'])
282
- def members(self, request, slug=None):
283
- """List organization members."""
284
- org = self.get_object()
285
- memberships = Membership.objects.filter(organization=org).select_related('user')
286
- data = [
287
- {
288
- 'user': UserSerializer(m.user).data,
289
- 'role': m.role,
290
- 'joined_at': m.joined_at
291
- }
292
- for m in memberships
293
- ]
294
- return Response(data)
295
-
296
- @action(detail=True, methods=['post'])
297
- def invite(self, request, slug=None):
298
- """Invite a user to the organization."""
299
- org = self.get_object()
300
- email = request.data.get('email')
301
- role = request.data.get('role', Membership.Role.MEMBER)
302
-
303
- try:
304
- user = User.objects.get(email=email)
305
- except User.DoesNotExist:
306
- return Response({'error': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
307
-
308
- membership, created = Membership.objects.get_or_create(
309
- user=user, organization=org,
310
- defaults={'role': role}
311
- )
312
-
313
- if not created:
314
- return Response({'error': 'User already a member'}, status=status.HTTP_400_BAD_REQUEST)
315
-
316
- return Response({'status': 'invited'}, status=status.HTTP_201_CREATED)
317
-
318
-
319
- class ProjectViewSet(viewsets.ModelViewSet):
320
- """Project viewset scoped to organization."""
321
- serializer_class = ProjectSerializer
322
- permission_classes = [permissions.IsAuthenticated]
323
-
324
- def get_queryset(self):
325
- org_slug = self.kwargs.get('org_slug')
326
- return Project.objects.filter(
327
- organization__slug=org_slug,
328
- organization__members=self.request.user,
329
- deleted_at__isnull=True
330
- ).select_related('created_by')
331
-
332
- def get_serializer_context(self):
333
- context = super().get_serializer_context()
334
- org_slug = self.kwargs.get('org_slug')
335
- context['organization'] = get_object_or_404(Organization, slug=org_slug)
336
- return context
337
-
338
- def perform_create(self, serializer):
339
- org_slug = self.kwargs.get('org_slug')
340
- org = get_object_or_404(Organization, slug=org_slug)
341
- serializer.save(organization=org, created_by=self.request.user)
342
-
343
- def perform_destroy(self, instance):
344
- # Soft delete instead of hard delete
345
- instance.soft_delete()
346
122
  ```
347
123
 
348
- ### 4. Custom Permissions and Authentication
349
-
350
- ```python
351
- from rest_framework import permissions
352
- from rest_framework.authentication import TokenAuthentication
353
- from rest_framework_simplejwt.authentication import JWTAuthentication
354
-
355
-
356
- class IsOrganizationMember(permissions.BasePermission):
357
- """Check if user is a member of the organization."""
358
-
359
- def has_permission(self, request, view):
360
- org_slug = view.kwargs.get('org_slug')
361
- if not org_slug:
362
- return True
363
- return Membership.objects.filter(
364
- user=request.user,
365
- organization__slug=org_slug
366
- ).exists()
367
-
368
-
369
- class IsOrganizationAdmin(permissions.BasePermission):
370
- """Check if user is an admin of the organization."""
371
-
372
- def has_permission(self, request, view):
373
- org_slug = view.kwargs.get('org_slug')
374
- if not org_slug:
375
- return False
376
- return Membership.objects.filter(
377
- user=request.user,
378
- organization__slug=org_slug,
379
- role__in=[Membership.Role.OWNER, Membership.Role.ADMIN]
380
- ).exists()
381
-
382
-
383
- class IsOwnerOrReadOnly(permissions.BasePermission):
384
- """Object-level permission for owner access."""
124
+ ## Workflows
385
125
 
386
- def has_object_permission(self, request, view, obj):
387
- if request.method in permissions.SAFE_METHODS:
388
- return True
126
+ ### API Development
389
127
 
390
- # Check various ownership patterns
391
- if hasattr(obj, 'owner'):
392
- return obj.owner == request.user
393
- if hasattr(obj, 'created_by'):
394
- return obj.created_by == request.user
395
- if hasattr(obj, 'user'):
396
- return obj.user == request.user
128
+ 1. Create model and migration
129
+ 2. Create serializer with validation
130
+ 3. Create ViewSet or APIView
131
+ 4. Configure URL routing
132
+ 5. Write tests with pytest-django
397
133
 
398
- return False
399
-
400
-
401
- # Custom JWT Authentication with additional claims
402
- from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
403
- from rest_framework_simplejwt.views import TokenObtainPairView
404
-
405
-
406
- class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
407
- """Add custom claims to JWT token."""
408
-
409
- @classmethod
410
- def get_token(cls, user):
411
- token = super().get_token(user)
412
- token['email'] = user.email
413
- token['is_verified'] = user.is_verified
414
- token['is_staff'] = user.is_staff
415
- return token
416
-
417
- def validate(self, attrs):
418
- data = super().validate(attrs)
419
- data['user'] = UserSerializer(self.user).data
420
- return data
421
-
422
-
423
- class CustomTokenObtainPairView(TokenObtainPairView):
424
- serializer_class = CustomTokenObtainPairSerializer
425
- ```
426
-
427
- ### 5. Query Optimization and Managers
134
+ ### Query Optimization
428
135
 
429
136
  ```python
430
- from django.db import models
431
- from django.db.models import Q, Count, Avg, F, Prefetch
432
-
137
+ # Avoid N+1 queries
138
+ users = User.objects.select_related('profile').prefetch_related(
139
+ Prefetch('organizations', queryset=Organization.objects.only('id', 'name'))
140
+ )
433
141
 
434
- class ProjectManager(models.Manager):
435
- """Custom manager with optimized queries."""
142
+ # Use values() for lightweight queries
143
+ User.objects.values('id', 'email', 'created_at')
436
144
 
437
- def get_queryset(self):
438
- return super().get_queryset().filter(deleted_at__isnull=True)
439
-
440
- def with_stats(self):
441
- """Include task statistics."""
442
- return self.annotate(
443
- task_count=Count('tasks'),
444
- completed_task_count=Count('tasks', filter=Q(tasks__status='completed')),
445
- completion_rate=F('completed_task_count') * 100.0 / F('task_count')
446
- )
447
-
448
- def for_user(self, user):
449
- """Filter projects accessible to user."""
450
- return self.filter(organization__members=user)
451
-
452
- def active(self):
453
- """Filter active projects."""
454
- return self.filter(status=Project.Status.ACTIVE)
455
-
456
- def with_recent_activity(self, days=7):
457
- """Filter projects with recent activity."""
458
- from datetime import timedelta
459
- from django.utils import timezone
460
- cutoff = timezone.now() - timedelta(days=days)
461
- return self.filter(
462
- Q(updated_at__gte=cutoff) | Q(tasks__updated_at__gte=cutoff)
463
- ).distinct()
464
-
465
-
466
- # Optimized queryset usage in views
467
- class OptimizedProjectViewSet(viewsets.ModelViewSet):
468
- """ViewSet demonstrating query optimization."""
469
-
470
- def get_queryset(self):
471
- return Project.objects.select_related(
472
- 'organization',
473
- 'created_by'
474
- ).prefetch_related(
475
- Prefetch(
476
- 'tasks',
477
- queryset=Task.objects.filter(status='active').only('id', 'title', 'status')
478
- ),
479
- 'organization__members'
480
- ).with_stats().for_user(self.request.user)
481
-
482
- def list(self, request, *args, **kwargs):
483
- # Use values() for lightweight list responses
484
- queryset = self.get_queryset().values(
485
- 'id', 'name', 'status', 'task_count', 'completion_rate', 'created_at'
486
- )
487
- return Response(list(queryset))
488
- ```
489
-
490
- ### 6. Async Views and Background Tasks
491
-
492
- ```python
493
- from django.http import JsonResponse
494
- from django.views import View
495
- from asgiref.sync import sync_to_async
496
- import asyncio
497
-
498
-
499
- class AsyncProjectView(View):
500
- """Async view for concurrent operations."""
501
-
502
- async def get(self, request, project_id):
503
- # Run multiple async operations concurrently
504
- project, tasks, activity = await asyncio.gather(
505
- self.get_project(project_id),
506
- self.get_tasks(project_id),
507
- self.get_recent_activity(project_id)
508
- )
509
-
510
- return JsonResponse({
511
- 'project': project,
512
- 'tasks': tasks,
513
- 'activity': activity
514
- })
515
-
516
- @sync_to_async
517
- def get_project(self, project_id):
518
- project = Project.objects.select_related('created_by').get(id=project_id)
519
- return {
520
- 'id': str(project.id),
521
- 'name': project.name,
522
- 'status': project.status,
523
- 'created_by': project.created_by.email
524
- }
525
-
526
- @sync_to_async
527
- def get_tasks(self, project_id):
528
- tasks = list(Task.objects.filter(project_id=project_id).values('id', 'title', 'status'))
529
- return tasks
530
-
531
- @sync_to_async
532
- def get_recent_activity(self, project_id):
533
- # Fetch recent activity logs
534
- from datetime import timedelta
535
- from django.utils import timezone
536
- cutoff = timezone.now() - timedelta(days=7)
537
- activities = list(ActivityLog.objects.filter(
538
- project_id=project_id,
539
- created_at__gte=cutoff
540
- ).values('action', 'created_at')[:10])
541
- return activities
542
-
543
-
544
- # Celery background tasks
545
- from celery import shared_task
546
- from django.core.mail import send_mail
547
-
548
-
549
- @shared_task(bind=True, max_retries=3)
550
- def send_invitation_email(self, user_id, org_id):
551
- """Send organization invitation email."""
552
- try:
553
- user = User.objects.get(id=user_id)
554
- org = Organization.objects.get(id=org_id)
555
-
556
- send_mail(
557
- subject=f'Invitation to join {org.name}',
558
- message=f'You have been invited to join {org.name}.',
559
- from_email='noreply@example.com',
560
- recipient_list=[user.email],
561
- fail_silently=False,
562
- )
563
- except Exception as exc:
564
- raise self.retry(exc=exc, countdown=60)
565
-
566
-
567
- @shared_task
568
- def generate_project_report(project_id):
569
- """Generate project report asynchronously."""
570
- project = Project.objects.prefetch_related('tasks').get(id=project_id)
571
-
572
- report_data = {
573
- 'project': project.name,
574
- 'total_tasks': project.tasks.count(),
575
- 'completed_tasks': project.tasks.filter(status='completed').count(),
576
- 'generated_at': timezone.now().isoformat()
577
- }
578
-
579
- # Save report to storage
580
- from django.core.files.base import ContentFile
581
- import json
582
-
583
- report_content = json.dumps(report_data, indent=2)
584
- project.latest_report.save(
585
- f'report_{project_id}.json',
586
- ContentFile(report_content.encode())
587
- )
588
-
589
- return report_data
590
- ```
591
-
592
- ### 7. Testing Patterns
593
-
594
- ```python
595
- import pytest
596
- from django.urls import reverse
597
- from rest_framework import status
598
- from rest_framework.test import APITestCase
599
- from model_bakery import baker
600
-
601
-
602
- class UserAPITestCase(APITestCase):
603
- """Test case for user API endpoints."""
604
-
605
- def setUp(self):
606
- self.user = baker.make(User, email='test@example.com')
607
- self.client.force_authenticate(user=self.user)
608
-
609
- def test_get_current_user(self):
610
- """Test retrieving current user profile."""
611
- url = reverse('user-me')
612
- response = self.client.get(url)
613
-
614
- self.assertEqual(response.status_code, status.HTTP_200_OK)
615
- self.assertEqual(response.data['email'], 'test@example.com')
616
-
617
- def test_update_current_user(self):
618
- """Test updating current user profile."""
619
- url = reverse('user-me')
620
- response = self.client.patch(url, {'username': 'newusername'})
621
-
622
- self.assertEqual(response.status_code, status.HTTP_200_OK)
623
- self.user.refresh_from_db()
624
- self.assertEqual(self.user.username, 'newusername')
625
-
626
- def test_create_user_with_weak_password(self):
627
- """Test user creation fails with weak password."""
628
- url = reverse('user-list')
629
- data = {
630
- 'email': 'new@example.com',
631
- 'username': 'newuser',
632
- 'password': '123',
633
- 'password_confirm': '123'
634
- }
635
-
636
- self.client.force_authenticate(user=None)
637
- response = self.client.post(url, data)
638
-
639
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
640
- self.assertIn('password', response.data)
641
-
642
-
643
- class OrganizationAPITestCase(APITestCase):
644
- """Test case for organization API endpoints."""
645
-
646
- def setUp(self):
647
- self.user = baker.make(User)
648
- self.org = baker.make(Organization, owner=self.user)
649
- baker.make(Membership, user=self.user, organization=self.org, role=Membership.Role.OWNER)
650
- self.client.force_authenticate(user=self.user)
651
-
652
- def test_list_user_organizations(self):
653
- """Test listing organizations user belongs to."""
654
- # Create another org user is not part of
655
- other_org = baker.make(Organization)
656
-
657
- url = reverse('organization-list')
658
- response = self.client.get(url)
659
-
660
- self.assertEqual(response.status_code, status.HTTP_200_OK)
661
- self.assertEqual(len(response.data), 1)
662
- self.assertEqual(response.data[0]['slug'], self.org.slug)
663
-
664
- def test_create_organization(self):
665
- """Test creating a new organization."""
666
- url = reverse('organization-list')
667
- data = {'name': 'New Org', 'slug': 'new-org'}
668
-
669
- response = self.client.post(url, data)
670
-
671
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
672
- self.assertEqual(response.data['name'], 'New Org')
673
- # Verify membership was created
674
- self.assertTrue(Membership.objects.filter(
675
- user=self.user,
676
- organization__slug='new-org',
677
- role=Membership.Role.OWNER
678
- ).exists())
679
-
680
- def test_invite_member(self):
681
- """Test inviting a member to organization."""
682
- new_user = baker.make(User, email='invite@example.com')
683
- url = reverse('organization-invite', kwargs={'slug': self.org.slug})
684
-
685
- response = self.client.post(url, {'email': 'invite@example.com', 'role': 'member'})
686
-
687
- self.assertEqual(response.status_code, status.HTTP_201_CREATED)
688
- self.assertTrue(Membership.objects.filter(
689
- user=new_user,
690
- organization=self.org
691
- ).exists())
692
-
693
-
694
- @pytest.fixture
695
- def api_client():
696
- """Pytest fixture for API client."""
697
- from rest_framework.test import APIClient
698
- return APIClient()
699
-
700
-
701
- @pytest.fixture
702
- def authenticated_client(api_client):
703
- """Pytest fixture for authenticated API client."""
704
- user = baker.make(User)
705
- api_client.force_authenticate(user=user)
706
- api_client.user = user
707
- return api_client
708
-
709
-
710
- @pytest.mark.django_db
711
- class TestProjectAPI:
712
- """Pytest-based tests for project API."""
713
-
714
- def test_create_project(self, authenticated_client):
715
- org = baker.make(Organization, owner=authenticated_client.user)
716
- baker.make(Membership, user=authenticated_client.user, organization=org)
717
-
718
- url = reverse('project-list', kwargs={'org_slug': org.slug})
719
- response = authenticated_client.post(url, {
720
- 'name': 'Test Project',
721
- 'description': 'A test project'
722
- })
723
-
724
- assert response.status_code == status.HTTP_201_CREATED
725
- assert response.data['name'] == 'Test Project'
726
- assert response.data['created_by']['id'] == str(authenticated_client.user.id)
727
-
728
- def test_soft_delete_project(self, authenticated_client):
729
- org = baker.make(Organization, owner=authenticated_client.user)
730
- baker.make(Membership, user=authenticated_client.user, organization=org)
731
- project = baker.make(Project, organization=org, created_by=authenticated_client.user)
732
-
733
- url = reverse('project-detail', kwargs={
734
- 'org_slug': org.slug,
735
- 'pk': project.id
736
- })
737
- response = authenticated_client.delete(url)
738
-
739
- assert response.status_code == status.HTTP_204_NO_CONTENT
740
- project.refresh_from_db()
741
- assert project.deleted_at is not None
145
+ # Annotate for aggregations
146
+ Organization.objects.annotate(member_count=Count('members'))
742
147
  ```
743
148
 
744
- ## Use Cases
745
-
746
- ### Multi-tenant SaaS Application
747
-
748
- ```python
749
- # settings.py - Multi-tenant configuration
750
- MIDDLEWARE = [
751
- 'django.middleware.security.SecurityMiddleware',
752
- 'django.contrib.sessions.middleware.SessionMiddleware',
753
- 'apps.tenants.middleware.TenantMiddleware', # Custom tenant middleware
754
- 'django.middleware.common.CommonMiddleware',
755
- # ...
756
- ]
757
-
758
- # middleware.py
759
- from django.utils.deprecation import MiddlewareMixin
760
- from threading import local
761
-
762
- _thread_locals = local()
763
-
764
- def get_current_tenant():
765
- return getattr(_thread_locals, 'tenant', None)
766
-
767
- class TenantMiddleware(MiddlewareMixin):
768
- def process_request(self, request):
769
- # Extract tenant from subdomain or header
770
- host = request.get_host().split(':')[0]
771
- subdomain = host.split('.')[0]
772
-
773
- try:
774
- tenant = Organization.objects.get(slug=subdomain)
775
- _thread_locals.tenant = tenant
776
- request.tenant = tenant
777
- except Organization.DoesNotExist:
778
- _thread_locals.tenant = None
779
- request.tenant = None
780
-
781
- # managers.py - Tenant-aware manager
782
- class TenantManager(models.Manager):
783
- def get_queryset(self):
784
- tenant = get_current_tenant()
785
- qs = super().get_queryset()
786
- if tenant:
787
- return qs.filter(organization=tenant)
788
- return qs
149
+ ## Best Practices
789
150
 
790
- # models.py - Tenant-scoped model
791
- class TenantModel(models.Model):
792
- organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
151
+ | Do | Avoid |
152
+ |----|-------|
153
+ | Use `select_related`/`prefetch_related` | N+1 queries |
154
+ | Use serializers for validation | Manual validation |
155
+ | Use custom managers | Query logic in views |
156
+ | Use signals sparingly | Overusing signals |
157
+ | Use Celery for heavy tasks | Sync operations for I/O |
793
158
 
794
- objects = TenantManager()
795
- all_objects = models.Manager() # Bypass tenant filtering
159
+ ## Project Structure
796
160
 
797
- class Meta:
798
- abstract = True
799
-
800
- def save(self, *args, **kwargs):
801
- if not self.organization_id:
802
- self.organization = get_current_tenant()
803
- super().save(*args, **kwargs)
804
161
  ```
805
-
806
- ### Real-time Dashboard with WebSockets
807
-
808
- ```python
809
- # consumers.py - Django Channels WebSocket consumer
810
- import json
811
- from channels.generic.websocket import AsyncWebsocketConsumer
812
- from channels.db import database_sync_to_async
813
-
814
-
815
- class DashboardConsumer(AsyncWebsocketConsumer):
816
- async def connect(self):
817
- self.user = self.scope['user']
818
- if not self.user.is_authenticated:
819
- await self.close()
820
- return
821
-
822
- self.project_id = self.scope['url_route']['kwargs']['project_id']
823
- self.room_group_name = f'dashboard_{self.project_id}'
824
-
825
- # Verify user has access to project
826
- if not await self.has_project_access():
827
- await self.close()
828
- return
829
-
830
- # Join room group
831
- await self.channel_layer.group_add(
832
- self.room_group_name,
833
- self.channel_name
834
- )
835
- await self.accept()
836
-
837
- # Send initial data
838
- await self.send_dashboard_data()
839
-
840
- async def disconnect(self, close_code):
841
- await self.channel_layer.group_discard(
842
- self.room_group_name,
843
- self.channel_name
844
- )
845
-
846
- async def receive(self, text_data):
847
- data = json.loads(text_data)
848
- action = data.get('action')
849
-
850
- if action == 'refresh':
851
- await self.send_dashboard_data()
852
- elif action == 'subscribe':
853
- # Handle subscription to specific metrics
854
- pass
855
-
856
- async def dashboard_update(self, event):
857
- """Handler for dashboard update messages."""
858
- await self.send(text_data=json.dumps({
859
- 'type': 'update',
860
- 'data': event['data']
861
- }))
862
-
863
- @database_sync_to_async
864
- def has_project_access(self):
865
- return Project.objects.filter(
866
- id=self.project_id,
867
- organization__members=self.user
868
- ).exists()
869
-
870
- @database_sync_to_async
871
- def get_dashboard_data(self):
872
- project = Project.objects.prefetch_related('tasks').get(id=self.project_id)
873
- return {
874
- 'project': {'id': str(project.id), 'name': project.name},
875
- 'stats': {
876
- 'total_tasks': project.tasks.count(),
877
- 'completed': project.tasks.filter(status='completed').count(),
878
- 'in_progress': project.tasks.filter(status='in_progress').count(),
879
- }
880
- }
881
-
882
- async def send_dashboard_data(self):
883
- data = await self.get_dashboard_data()
884
- await self.send(text_data=json.dumps({
885
- 'type': 'initial',
886
- 'data': data
887
- }))
888
-
889
-
890
- # Signal to broadcast updates
891
- from django.db.models.signals import post_save
892
- from django.dispatch import receiver
893
- from channels.layers import get_channel_layer
894
- from asgiref.sync import async_to_sync
895
-
896
-
897
- @receiver(post_save, sender=Task)
898
- def broadcast_task_update(sender, instance, **kwargs):
899
- channel_layer = get_channel_layer()
900
- async_to_sync(channel_layer.group_send)(
901
- f'dashboard_{instance.project_id}',
902
- {
903
- 'type': 'dashboard_update',
904
- 'data': {
905
- 'task_id': str(instance.id),
906
- 'status': instance.status,
907
- 'updated_at': instance.updated_at.isoformat()
908
- }
909
- }
910
- )
162
+ project/
163
+ ├── manage.py
164
+ ├── config/
165
+ │ ├── settings/
166
+ │ ├── urls.py
167
+ │ └── wsgi.py
168
+ ├── apps/
169
+ │ ├── users/
170
+ │ │ ├── models.py
171
+ │ │ ├── serializers.py
172
+ │ │ ├── views.py
173
+ │ │ └── tests/
174
+ │ └── organizations/
175
+ ├── common/
176
+ │ ├── permissions.py
177
+ │ └── pagination.py
178
+ └── tests/
911
179
  ```
912
180
 
913
- ## Best Practices
914
-
915
- ### Do's
916
-
917
- - Use UUID primary keys for public-facing IDs
918
- - Use `select_related` and `prefetch_related` for query optimization
919
- - Use custom managers for reusable query logic
920
- - Use signals sparingly and for cross-cutting concerns only
921
- - Use Django REST Framework serializers for validation
922
- - Use soft deletes for important data
923
- - Use database indexes for frequently queried fields
924
- - Write comprehensive tests with fixtures
925
- - Use environment variables for configuration
926
- - Use Celery for background tasks
927
-
928
- ### Don'ts
929
-
930
- - Don't use `ForeignKey` without `on_delete` consideration
931
- - Don't query in loops (N+1 problem)
932
- - Don't store sensitive data in plain text
933
- - Don't use `Model.objects.all()` in production views
934
- - Don't skip migrations in deployment
935
- - Don't use raw SQL without parameterization
936
- - Don't ignore database connection pooling
937
- - Don't put business logic in views
938
- - Don't use synchronous operations for I/O-heavy tasks
939
- - Don't skip input validation
940
-
941
- ## References
942
-
943
- - [Django Documentation](https://docs.djangoproject.com/)
944
- - [Django REST Framework](https://www.django-rest-framework.org/)
945
- - [Django Channels](https://channels.readthedocs.io/)
946
- - [Celery Documentation](https://docs.celeryq.dev/)
947
- - [Django Best Practices](https://django-best-practices.readthedocs.io/)
181
+ For detailed examples and patterns, see reference files above.