ntc-dev-kit 1.0.0__py3-none-any.whl
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.
- ntc/__init__.py +5 -0
- ntc/app_creator.py +296 -0
- ntc/cli.py +4 -0
- ntc/cli_enhanced.py +820 -0
- ntc/composer/__init__.py +0 -0
- ntc/composer/main.py +443 -0
- ntc/deployment/__init__.py +0 -0
- ntc/templates/__init__.py +2 -0
- ntc/templates/auth_app/__init__.py +0 -0
- ntc/templates/auth_app/admin.py +24 -0
- ntc/templates/auth_app/apps.py +6 -0
- ntc/templates/auth_app/models.py +30 -0
- ntc/templates/auth_app/serializers.py +101 -0
- ntc/templates/auth_app/urls.py +19 -0
- ntc/templates/auth_app/views.py +175 -0
- ntc/templates/drf_structure/__init__.py +1 -0
- ntc/templates/drf_structure/drf_structure.md +83 -0
- ntc/templates/drf_structure/main.py +389 -0
- ntc/templates/drf_structure/structure.json +126 -0
- ntc/templates/drf_structure/templates/.entry/start_core_backend.sh +37 -0
- ntc/templates/drf_structure/templates/.entry/start_tasks_backend.sh +30 -0
- ntc/templates/drf_structure/templates/CLAUDE.md +194 -0
- ntc/templates/drf_structure/templates/README.md +117 -0
- ntc/templates/drf_structure/templates/apps/app_admin.py +3 -0
- ntc/templates/drf_structure/templates/apps/app_apps.py +6 -0
- ntc/templates/drf_structure/templates/apps/app_models.py +4 -0
- ntc/templates/drf_structure/templates/apps/app_urls.py +8 -0
- ntc/templates/drf_structure/templates/apps/app_views.py +6 -0
- ntc/templates/drf_structure/templates/apps/common_apps.py +6 -0
- ntc/templates/drf_structure/templates/apps/common_models.py +8 -0
- ntc/templates/drf_structure/templates/config/gunicorn_config.py +9 -0
- ntc/templates/drf_structure/templates/config/http.conf +33 -0
- ntc/templates/drf_structure/templates/core/base_model.py +9 -0
- ntc/templates/drf_structure/templates/core/formatter.py +5 -0
- ntc/templates/drf_structure/templates/core/logger.py +8 -0
- ntc/templates/drf_structure/templates/core/pagination.py +6 -0
- ntc/templates/drf_structure/templates/core/response_wrapper.py +7 -0
- ntc/templates/drf_structure/templates/core/services/azure/email/backend.py +108 -0
- ntc/templates/drf_structure/templates/core/services/azure/email/client.py +296 -0
- ntc/templates/drf_structure/templates/core/services/azure/email/notifier.py +102 -0
- ntc/templates/drf_structure/templates/core/services/object_storage.py +285 -0
- ntc/templates/drf_structure/templates/django_project/asgi.py +5 -0
- ntc/templates/drf_structure/templates/django_project/celery.py +8 -0
- ntc/templates/drf_structure/templates/django_project/dev_settings.py +2 -0
- ntc/templates/drf_structure/templates/django_project/django_settings.txt +322 -0
- ntc/templates/drf_structure/templates/django_project/management_command.py +114 -0
- ntc/templates/drf_structure/templates/django_project/prod_settings.py +3 -0
- ntc/templates/drf_structure/templates/django_project/schema.py +7 -0
- ntc/templates/drf_structure/templates/django_project/urls.py +6 -0
- ntc/templates/drf_structure/templates/django_project/wsgi.py +5 -0
- ntc/templates/drf_structure/templates/docker/docker-compose.yml +11 -0
- ntc/templates/drf_structure/templates/knowledge/knowledge_description.md +201 -0
- ntc/templates/drf_structure/templates/knowledge/knowledge_design_pattern.md +722 -0
- ntc/templates/drf_structure/templates/knowledge/knowledge_structure.md +151 -0
- ntc/templates/drf_structure/templates/root/README.md +307 -0
- ntc/templates/drf_structure/templates/root/django_env.txt +98 -0
- ntc/templates/drf_structure/templates/root/django_requirements.txt +24 -0
- ntc/templates/drf_structure/templates/root/gitignore.txt +6 -0
- ntc/templates/drf_structure/templates/root/manage.py +11 -0
- ntc/templates/flutter/.gitignore +44 -0
- ntc/templates/flutter/.knowledge/knowledge_description.md +215 -0
- ntc/templates/flutter/.knowledge/knowledge_design_pattern.md +310 -0
- ntc/templates/flutter/.knowledge/knowledge_structure.md +83 -0
- ntc/templates/flutter/CLAUDE.md +190 -0
- ntc/templates/flutter/__init__.py +0 -0
- ntc/templates/flutter/analysis_options.yaml +9 -0
- ntc/templates/flutter/main.py +187 -0
- ntc/templates/flutter/pubspec.yaml +65 -0
- ntc/templates/flutter/structure.json +51 -0
- ntc/templates/nextjs/.env.example +10 -0
- ntc/templates/nextjs/.gitignore +36 -0
- ntc/templates/nextjs/CLAUDE.md +181 -0
- ntc/templates/nextjs/README.md +94 -0
- ntc/templates/nextjs/__init__.py +0 -0
- ntc/templates/nextjs/knowledge/knowledge_description.md +208 -0
- ntc/templates/nextjs/knowledge/knowledge_design_pattern.md +460 -0
- ntc/templates/nextjs/knowledge/knowledge_structure.md +75 -0
- ntc/templates/nextjs/main.py +224 -0
- ntc/templates/nextjs/package.json +25 -0
- ntc/templates/nextjs/src/app/globals.css +26 -0
- ntc/templates/nextjs/src/app/layout.tsx +59 -0
- ntc/templates/nextjs/src/app/page.tsx +21 -0
- ntc/templates/nextjs/src/components/Header.tsx +16 -0
- ntc/templates/nextjs/src/services/api.ts +81 -0
- ntc/templates/nextjs/src/utils/constants.ts +1 -0
- ntc/templates/nextjs/structure.json +32 -0
- ntc/templates/nextjs/tsconfig.json +27 -0
- ntc/templates/react_admin/.env.example +1 -0
- ntc/templates/react_admin/.gitignore +10 -0
- ntc/templates/react_admin/CLAUDE.md +210 -0
- ntc/templates/react_admin/README.md +72 -0
- ntc/templates/react_admin/__init__.py +0 -0
- ntc/templates/react_admin/index.html +13 -0
- ntc/templates/react_admin/knowledge/knowledge_description.md +202 -0
- ntc/templates/react_admin/knowledge/knowledge_design_pattern.md +448 -0
- ntc/templates/react_admin/knowledge/knowledge_structure.md +97 -0
- ntc/templates/react_admin/main.py +208 -0
- ntc/templates/react_admin/package.json +35 -0
- ntc/templates/react_admin/src/App.tsx +60 -0
- ntc/templates/react_admin/src/components/ProtectedRoute.tsx +16 -0
- ntc/templates/react_admin/src/components/Sidebar.tsx +90 -0
- ntc/templates/react_admin/src/components/TopNav.tsx +44 -0
- ntc/templates/react_admin/src/context/AuthContext.tsx +107 -0
- ntc/templates/react_admin/src/context/ThemeContext.tsx +34 -0
- ntc/templates/react_admin/src/context/WorkspaceContext.tsx +28 -0
- ntc/templates/react_admin/src/hooks/useApi.ts +39 -0
- ntc/templates/react_admin/src/index.css +34 -0
- ntc/templates/react_admin/src/layouts/AuthLayout.tsx +23 -0
- ntc/templates/react_admin/src/layouts/DashboardLayout.tsx +24 -0
- ntc/templates/react_admin/src/main.tsx +5 -0
- ntc/templates/react_admin/src/pages/DashboardPage.tsx +10 -0
- ntc/templates/react_admin/src/pages/auth/LoginPage.tsx +79 -0
- ntc/templates/react_admin/src/pages/auth/RegisterPage.tsx +93 -0
- ntc/templates/react_admin/src/services/api.service.ts +44 -0
- ntc/templates/react_admin/src/services/base.service.ts +193 -0
- ntc/templates/react_admin/structure.json +52 -0
- ntc/templates/react_admin/tsconfig.app.json +25 -0
- ntc/templates/react_admin/tsconfig.json +7 -0
- ntc/templates/react_admin/tsconfig.node.json +19 -0
- ntc/templates/react_admin/vite.config.ts +13 -0
- ntc/templates/react_structure/CLAUDE.md +176 -0
- ntc/templates/react_structure/__init__.py +1 -0
- ntc/templates/react_structure/index.css +21 -0
- ntc/templates/react_structure/index.html +13 -0
- ntc/templates/react_structure/knowledge/knowledge_description.md +170 -0
- ntc/templates/react_structure/knowledge/knowledge_design_pattern.md +411 -0
- ntc/templates/react_structure/knowledge/knowledge_structure.md +74 -0
- ntc/templates/react_structure/main.py +217 -0
- ntc/templates/react_structure/package.json +32 -0
- ntc/templates/react_structure/src/App.tsx +63 -0
- ntc/templates/react_structure/src/components/Header.tsx +51 -0
- ntc/templates/react_structure/src/main.tsx +5 -0
- ntc/templates/react_structure/src/services/api.ts +90 -0
- ntc/templates/react_structure/src/utils/constants.ts +1 -0
- ntc/templates/react_structure/structure.json +65 -0
- ntc/templates/react_structure/tsconfig.app.json +25 -0
- ntc/templates/react_structure/tsconfig.json +7 -0
- ntc/templates/react_structure/tsconfig.node.json +19 -0
- ntc/templates/react_structure/vite.config.ts +12 -0
- ntc/utils/__init__.py +1 -0
- ntc/utils/environment.py +67 -0
- ntc/utils/output.py +41 -0
- ntc_dev_kit-1.0.0.dist-info/METADATA +188 -0
- ntc_dev_kit-1.0.0.dist-info/RECORD +148 -0
- ntc_dev_kit-1.0.0.dist-info/WHEEL +5 -0
- ntc_dev_kit-1.0.0.dist-info/entry_points.txt +2 -0
- ntc_dev_kit-1.0.0.dist-info/licenses/LICENSE +21 -0
- ntc_dev_kit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
# Design Patterns - {project_name}
|
|
2
|
+
|
|
3
|
+
This document outlines the design patterns, architectural decisions, and coding conventions used in this NTC-generated Django REST Framework project.
|
|
4
|
+
|
|
5
|
+
## 🏗️ Architectural Patterns
|
|
6
|
+
|
|
7
|
+
### **1. Service/Selector Pattern**
|
|
8
|
+
|
|
9
|
+
**Purpose**: Separate business logic from views and data access logic.
|
|
10
|
+
|
|
11
|
+
**Structure**:
|
|
12
|
+
```
|
|
13
|
+
apps/myapp/
|
|
14
|
+
├── views.py # HTTP request/response handling only
|
|
15
|
+
├── services/ # Business logic and operations
|
|
16
|
+
├── selectors/ # Data querying and filtering
|
|
17
|
+
└── serializers/ # Data validation and transformation
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Example Implementation**:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
# selectors/user_selector.py
|
|
24
|
+
from django.db.models import QuerySet
|
|
25
|
+
from apps.users.models import User
|
|
26
|
+
|
|
27
|
+
def get_active_users() -> QuerySet[User]:
|
|
28
|
+
"""Get all active users."""
|
|
29
|
+
return User.objects.filter(is_active=True)
|
|
30
|
+
|
|
31
|
+
def get_user_by_email(email: str) -> User:
|
|
32
|
+
"""Get user by email address."""
|
|
33
|
+
return User.objects.get(email=email)
|
|
34
|
+
|
|
35
|
+
# services/user_service.py
|
|
36
|
+
from django.contrib.auth import authenticate
|
|
37
|
+
from apps.users.models import User
|
|
38
|
+
from apps.users.selectors.user_selector import get_user_by_email
|
|
39
|
+
|
|
40
|
+
def create_user(email: str, password: str, **kwargs) -> User:
|
|
41
|
+
"""Create a new user with validation."""
|
|
42
|
+
if User.objects.filter(email=email).exists():
|
|
43
|
+
raise ValueError("User with this email already exists")
|
|
44
|
+
|
|
45
|
+
user = User.objects.create_user(
|
|
46
|
+
email=email,
|
|
47
|
+
password=password,
|
|
48
|
+
**kwargs
|
|
49
|
+
)
|
|
50
|
+
return user
|
|
51
|
+
|
|
52
|
+
def authenticate_user(email: str, password: str) -> User:
|
|
53
|
+
"""Authenticate user credentials."""
|
|
54
|
+
user = authenticate(email=email, password=password)
|
|
55
|
+
if not user:
|
|
56
|
+
raise ValueError("Invalid credentials")
|
|
57
|
+
return user
|
|
58
|
+
|
|
59
|
+
# views.py
|
|
60
|
+
from rest_framework.decorators import api_view
|
|
61
|
+
from core.response_wrapper import success_response, error_response
|
|
62
|
+
from .services.user_service import create_user, authenticate_user
|
|
63
|
+
from .selectors.user_selector import get_active_users
|
|
64
|
+
|
|
65
|
+
@api_view(['POST'])
|
|
66
|
+
def create_user_view(request):
|
|
67
|
+
"""Create a new user."""
|
|
68
|
+
try:
|
|
69
|
+
user = create_user(
|
|
70
|
+
email=request.data['email'],
|
|
71
|
+
password=request.data['password']
|
|
72
|
+
)
|
|
73
|
+
return success_response(
|
|
74
|
+
data={"user_id": user.id},
|
|
75
|
+
message="User created successfully"
|
|
76
|
+
)
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
return error_response(message=str(e), code=400)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### **2. Repository Pattern (via Selectors)**
|
|
82
|
+
|
|
83
|
+
**Purpose**: Abstract data access logic and provide a consistent interface for querying data.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# selectors/base_selector.py
|
|
87
|
+
from typing import Optional, List
|
|
88
|
+
from django.db.models import Model, QuerySet
|
|
89
|
+
|
|
90
|
+
class BaseSelector:
|
|
91
|
+
model = None
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def get_by_id(cls, id: int) -> Optional[Model]:
|
|
95
|
+
"""Get object by ID."""
|
|
96
|
+
try:
|
|
97
|
+
return cls.model.objects.get(id=id)
|
|
98
|
+
except cls.model.DoesNotExist:
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def get_all(cls) -> QuerySet:
|
|
103
|
+
"""Get all objects."""
|
|
104
|
+
return cls.model.objects.all()
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def filter_by(cls, **kwargs) -> QuerySet:
|
|
108
|
+
"""Filter objects by given criteria."""
|
|
109
|
+
return cls.model.objects.filter(**kwargs)
|
|
110
|
+
|
|
111
|
+
# selectors/user_selector.py
|
|
112
|
+
from .base_selector import BaseSelector
|
|
113
|
+
from apps.users.models import User
|
|
114
|
+
|
|
115
|
+
class UserSelector(BaseSelector):
|
|
116
|
+
model = User
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def get_active_users(cls) -> QuerySet:
|
|
120
|
+
"""Get all active users."""
|
|
121
|
+
return cls.filter_by(is_active=True)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### **3. Factory Pattern (for Model Creation)**
|
|
125
|
+
|
|
126
|
+
**Purpose**: Centralize object creation logic with proper validation.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# services/factories/user_factory.py
|
|
130
|
+
from apps.users.models import User
|
|
131
|
+
from apps.common.models import TimeStampedModel
|
|
132
|
+
|
|
133
|
+
class UserFactory:
|
|
134
|
+
@staticmethod
|
|
135
|
+
def create_user(email: str, password: str, **kwargs) -> User:
|
|
136
|
+
"""Create user with proper validation."""
|
|
137
|
+
# Validation logic
|
|
138
|
+
if not email:
|
|
139
|
+
raise ValueError("Email is required")
|
|
140
|
+
|
|
141
|
+
# Creation logic
|
|
142
|
+
user = User.objects.create_user(
|
|
143
|
+
email=email,
|
|
144
|
+
password=password,
|
|
145
|
+
**kwargs
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Post-creation logic (send welcome email, etc.)
|
|
149
|
+
# ...
|
|
150
|
+
|
|
151
|
+
return user
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 🎯 Response Pattern
|
|
155
|
+
|
|
156
|
+
### **Standardized API Responses**
|
|
157
|
+
|
|
158
|
+
**Purpose**: Consistent response format across all endpoints.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
# core/response_wrapper.py
|
|
162
|
+
from rest_framework.response import Response
|
|
163
|
+
|
|
164
|
+
def success_response(data=None, message='Success', status=200):
|
|
165
|
+
"""Standard success response format."""
|
|
166
|
+
return Response({
|
|
167
|
+
'status': 'success',
|
|
168
|
+
'message': message,
|
|
169
|
+
'data': data
|
|
170
|
+
}, status=status)
|
|
171
|
+
|
|
172
|
+
def error_response(message='Error', code=400, errors=None):
|
|
173
|
+
"""Standard error response format."""
|
|
174
|
+
response_data = {
|
|
175
|
+
'status': 'error',
|
|
176
|
+
'message': message
|
|
177
|
+
}
|
|
178
|
+
if errors:
|
|
179
|
+
response_data['errors'] = errors
|
|
180
|
+
|
|
181
|
+
return Response(response_data, status=code)
|
|
182
|
+
|
|
183
|
+
# Usage in views
|
|
184
|
+
@api_view(['GET'])
|
|
185
|
+
def get_users(request):
|
|
186
|
+
users = get_active_users()
|
|
187
|
+
serializer = UserSerializer(users, many=True)
|
|
188
|
+
return success_response(
|
|
189
|
+
data=serializer.data,
|
|
190
|
+
message="Users retrieved successfully"
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## 🗄️ Model Patterns
|
|
195
|
+
|
|
196
|
+
### **1. Abstract Base Models**
|
|
197
|
+
|
|
198
|
+
**Purpose**: Provide common fields and functionality across models.
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
# apps/common/models.py
|
|
202
|
+
from django.db import models
|
|
203
|
+
|
|
204
|
+
class TimeStampedModel(models.Model):
|
|
205
|
+
"""Abstract model with timestamp fields."""
|
|
206
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
207
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
208
|
+
|
|
209
|
+
class Meta:
|
|
210
|
+
abstract = True
|
|
211
|
+
|
|
212
|
+
# core/base_model.py
|
|
213
|
+
from django.db import models
|
|
214
|
+
|
|
215
|
+
class BaseModel(models.Model):
|
|
216
|
+
"""Enhanced base model with common fields."""
|
|
217
|
+
id = models.BigAutoField(primary_key=True)
|
|
218
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
219
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
220
|
+
|
|
221
|
+
class Meta:
|
|
222
|
+
abstract = True
|
|
223
|
+
|
|
224
|
+
def __str__(self):
|
|
225
|
+
return f"{self.__class__.__name__}({self.id})"
|
|
226
|
+
|
|
227
|
+
# Usage in app models
|
|
228
|
+
from apps.common.models import TimeStampedModel
|
|
229
|
+
|
|
230
|
+
class User(TimeStampedModel):
|
|
231
|
+
email = models.EmailField(unique=True)
|
|
232
|
+
first_name = models.CharField(max_length=100)
|
|
233
|
+
# ... other fields
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### **2. Manager Pattern**
|
|
237
|
+
|
|
238
|
+
**Purpose**: Custom query methods at the model level.
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# apps/users/models.py
|
|
242
|
+
from django.contrib.auth.models import AbstractUser
|
|
243
|
+
from django.db import models
|
|
244
|
+
|
|
245
|
+
class ActiveUserManager(models.Manager):
|
|
246
|
+
"""Manager for active users only."""
|
|
247
|
+
def get_queryset(self):
|
|
248
|
+
return super().get_queryset().filter(is_active=True)
|
|
249
|
+
|
|
250
|
+
class User(AbstractUser):
|
|
251
|
+
email = models.EmailField(unique=True)
|
|
252
|
+
|
|
253
|
+
# Managers
|
|
254
|
+
objects = models.Manager() # Default manager
|
|
255
|
+
active = ActiveUserManager() # Custom manager
|
|
256
|
+
|
|
257
|
+
USERNAME_FIELD = 'email'
|
|
258
|
+
REQUIRED_FIELDS = []
|
|
259
|
+
|
|
260
|
+
# Usage
|
|
261
|
+
active_users = User.active.all() # Only active users
|
|
262
|
+
all_users = User.objects.all() # All users
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## 🔐 Authentication Patterns
|
|
266
|
+
|
|
267
|
+
### **JWT Token Pattern**
|
|
268
|
+
|
|
269
|
+
**Purpose**: Stateless authentication with refresh token support.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
# services/auth_service.py
|
|
273
|
+
from rest_framework_simplejwt.tokens import RefreshToken
|
|
274
|
+
from django.contrib.auth import authenticate
|
|
275
|
+
|
|
276
|
+
class AuthService:
|
|
277
|
+
@staticmethod
|
|
278
|
+
def login_user(email: str, password: str) -> dict:
|
|
279
|
+
"""Authenticate user and return tokens."""
|
|
280
|
+
user = authenticate(email=email, password=password)
|
|
281
|
+
if not user:
|
|
282
|
+
raise ValueError("Invalid credentials")
|
|
283
|
+
|
|
284
|
+
refresh = RefreshToken.for_user(user)
|
|
285
|
+
return {
|
|
286
|
+
'refresh': str(refresh),
|
|
287
|
+
'access': str(refresh.access_token),
|
|
288
|
+
'user': {
|
|
289
|
+
'id': user.id,
|
|
290
|
+
'email': user.email,
|
|
291
|
+
'first_name': user.first_name,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def refresh_token(refresh_token: str) -> dict:
|
|
297
|
+
"""Refresh access token."""
|
|
298
|
+
try:
|
|
299
|
+
refresh = RefreshToken(refresh_token)
|
|
300
|
+
return {
|
|
301
|
+
'access': str(refresh.access_token)
|
|
302
|
+
}
|
|
303
|
+
except Exception:
|
|
304
|
+
raise ValueError("Invalid refresh token")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## 📊 Serializer Patterns
|
|
308
|
+
|
|
309
|
+
### **Nested Serializer Pattern**
|
|
310
|
+
|
|
311
|
+
**Purpose**: Handle complex data relationships efficiently.
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
# serializers/user_serializer.py
|
|
315
|
+
from rest_framework import serializers
|
|
316
|
+
from apps.users.models import User, Profile
|
|
317
|
+
|
|
318
|
+
class ProfileSerializer(serializers.ModelSerializer):
|
|
319
|
+
class Meta:
|
|
320
|
+
model = Profile
|
|
321
|
+
fields = ['bio', 'avatar', 'phone']
|
|
322
|
+
|
|
323
|
+
class UserSerializer(serializers.ModelSerializer):
|
|
324
|
+
profile = ProfileSerializer(read_only=True)
|
|
325
|
+
full_name = serializers.SerializerMethodField()
|
|
326
|
+
|
|
327
|
+
class Meta:
|
|
328
|
+
model = User
|
|
329
|
+
fields = ['id', 'email', 'first_name', 'last_name', 'full_name', 'profile']
|
|
330
|
+
|
|
331
|
+
def get_full_name(self, obj):
|
|
332
|
+
return f"{obj.first_name} {obj.last_name}".strip()
|
|
333
|
+
|
|
334
|
+
class UserCreateSerializer(serializers.ModelSerializer):
|
|
335
|
+
password = serializers.CharField(write_only=True)
|
|
336
|
+
|
|
337
|
+
class Meta:
|
|
338
|
+
model = User
|
|
339
|
+
fields = ['email', 'first_name', 'last_name', 'password']
|
|
340
|
+
|
|
341
|
+
def create(self, validated_data):
|
|
342
|
+
password = validated_data.pop('password')
|
|
343
|
+
user = User.objects.create_user(password=password, **validated_data)
|
|
344
|
+
return user
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## 🔄 Task Patterns (Celery)
|
|
348
|
+
|
|
349
|
+
### **Background Task Pattern**
|
|
350
|
+
|
|
351
|
+
**Purpose**: Handle long-running operations asynchronously.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
# tasks/email_tasks.py
|
|
355
|
+
from celery import shared_task
|
|
356
|
+
from django.core.mail import send_mail
|
|
357
|
+
from django.conf import settings
|
|
358
|
+
|
|
359
|
+
@shared_task
|
|
360
|
+
def send_welcome_email(user_id: int):
|
|
361
|
+
"""Send welcome email to new user."""
|
|
362
|
+
from apps.users.models import User
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
user = User.objects.get(id=user_id)
|
|
366
|
+
send_mail(
|
|
367
|
+
subject='Welcome to {project_name}',
|
|
368
|
+
message=f'Welcome {user.first_name}!',
|
|
369
|
+
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
370
|
+
recipient_list=[user.email],
|
|
371
|
+
)
|
|
372
|
+
return f"Welcome email sent to {user.email}"
|
|
373
|
+
except User.DoesNotExist:
|
|
374
|
+
return f"User with ID {user_id} not found"
|
|
375
|
+
|
|
376
|
+
# Usage in services
|
|
377
|
+
from .tasks.email_tasks import send_welcome_email
|
|
378
|
+
|
|
379
|
+
def create_user(email: str, password: str) -> User:
|
|
380
|
+
user = User.objects.create_user(email=email, password=password)
|
|
381
|
+
|
|
382
|
+
# Send welcome email asynchronously
|
|
383
|
+
send_welcome_email.delay(user.id)
|
|
384
|
+
|
|
385
|
+
return user
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## 🧪 Testing Patterns
|
|
389
|
+
|
|
390
|
+
### **Test Organization Pattern**
|
|
391
|
+
|
|
392
|
+
**Purpose**: Structured testing approach with proper fixtures.
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
# tests/test_user_service.py
|
|
396
|
+
from django.test import TestCase
|
|
397
|
+
from apps.users.services.user_service import create_user, authenticate_user
|
|
398
|
+
from apps.users.models import User
|
|
399
|
+
|
|
400
|
+
class UserServiceTest(TestCase):
|
|
401
|
+
def setUp(self):
|
|
402
|
+
"""Set up test data."""
|
|
403
|
+
self.user_data = {
|
|
404
|
+
'email': 'test@example.com',
|
|
405
|
+
'password': 'testpass123',
|
|
406
|
+
'first_name': 'Test'
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
def test_create_user_success(self):
|
|
410
|
+
"""Test successful user creation."""
|
|
411
|
+
user = create_user(**self.user_data)
|
|
412
|
+
|
|
413
|
+
self.assertIsInstance(user, User)
|
|
414
|
+
self.assertEqual(user.email, self.user_data['email'])
|
|
415
|
+
self.assertTrue(user.check_password(self.user_data['password']))
|
|
416
|
+
|
|
417
|
+
def test_create_user_duplicate_email(self):
|
|
418
|
+
"""Test user creation with duplicate email."""
|
|
419
|
+
create_user(**self.user_data)
|
|
420
|
+
|
|
421
|
+
with self.assertRaises(ValueError):
|
|
422
|
+
create_user(**self.user_data)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## 📋 Naming Conventions
|
|
426
|
+
|
|
427
|
+
### **File and Directory Naming**
|
|
428
|
+
- **Apps**: `snake_case` (e.g., `user_management`)
|
|
429
|
+
- **Models**: `PascalCase` (e.g., `UserProfile`)
|
|
430
|
+
- **Views**: `snake_case` functions (e.g., `create_user_view`)
|
|
431
|
+
- **Services**: `snake_case` functions (e.g., `create_user`)
|
|
432
|
+
- **URLs**: `kebab-case` (e.g., `/api/user-profile/`)
|
|
433
|
+
|
|
434
|
+
### **Variable Naming**
|
|
435
|
+
- **Python**: `snake_case`
|
|
436
|
+
- **Constants**: `UPPER_SNAKE_CASE`
|
|
437
|
+
- **Private methods**: `_leading_underscore`
|
|
438
|
+
- **Class methods**: `snake_case`
|
|
439
|
+
|
|
440
|
+
## 🎯 Error Handling Patterns
|
|
441
|
+
|
|
442
|
+
### **Centralized Error Handling**
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
# core/exceptions.py
|
|
446
|
+
class BusinessLogicError(Exception):
|
|
447
|
+
"""Custom exception for business logic errors."""
|
|
448
|
+
def __init__(self, message, code=400):
|
|
449
|
+
self.message = message
|
|
450
|
+
self.code = code
|
|
451
|
+
super().__init__(self.message)
|
|
452
|
+
|
|
453
|
+
# services/base_service.py
|
|
454
|
+
from core.exceptions import BusinessLogicError
|
|
455
|
+
|
|
456
|
+
class BaseService:
|
|
457
|
+
@staticmethod
|
|
458
|
+
def validate_required_fields(data: dict, required_fields: list):
|
|
459
|
+
"""Validate required fields in data."""
|
|
460
|
+
missing_fields = [field for field in required_fields if not data.get(field)]
|
|
461
|
+
if missing_fields:
|
|
462
|
+
raise BusinessLogicError(
|
|
463
|
+
f"Missing required fields: {', '.join(missing_fields)}"
|
|
464
|
+
)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## 🌐 External Service Patterns
|
|
468
|
+
|
|
469
|
+
### **Azure Communication Services Email Pattern**
|
|
470
|
+
|
|
471
|
+
**Purpose**: Send emails through Azure Communication Services with template support.
|
|
472
|
+
|
|
473
|
+
```python
|
|
474
|
+
# core/services/azure/email/notifier.py
|
|
475
|
+
from core.services.azure.email import send_template_email
|
|
476
|
+
|
|
477
|
+
# Send templated email
|
|
478
|
+
send_template_email(
|
|
479
|
+
subject="Welcome to {project_name}",
|
|
480
|
+
to="user@example.com",
|
|
481
|
+
template="welcome_email", # Looks for welcome_email.html and welcome_email.txt
|
|
482
|
+
context={"name": "John", "verification_url": "https://..."},
|
|
483
|
+
sender="notification", # or "no_reply", "support", "marketing"
|
|
484
|
+
fail_silently=False
|
|
485
|
+
)
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Template Structure**:
|
|
489
|
+
```
|
|
490
|
+
templates/
|
|
491
|
+
├── welcome_email.html # Required HTML template
|
|
492
|
+
└── welcome_email.txt # Optional plain text template (auto-generated if missing)
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Sender Configuration**:
|
|
496
|
+
- `no_reply`: For automated system emails
|
|
497
|
+
- `notification`: For user notifications
|
|
498
|
+
- `support`: For support emails
|
|
499
|
+
- `marketing`: For marketing emails
|
|
500
|
+
|
|
501
|
+
**Environment Variables**:
|
|
502
|
+
```bash
|
|
503
|
+
AZURE_COMMUNICATION_ENDPOINT=https://xxx.communication.azure.com
|
|
504
|
+
AZURE_COMMUNICATION_ACCESS_KEY=your-access-key
|
|
505
|
+
AZURE_COMMUNICATION_EMAIL_SENDER_NOTIFICATION=DoNotReply@xxx.azurecomm.net
|
|
506
|
+
AZURE_COMMUNICATION_EMAIL_SENDER_NO_REPLY=DoNotReply@xxx.azurecomm.net
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### **Cloudflare R2 Object Storage Pattern**
|
|
510
|
+
|
|
511
|
+
**Purpose**: Store and manage files in Cloudflare R2 (S3-compatible storage).
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
from core.services.object_storage import ObjectStorageService
|
|
515
|
+
|
|
516
|
+
# Initialize storage service
|
|
517
|
+
storage = ObjectStorageService()
|
|
518
|
+
|
|
519
|
+
# Upload file
|
|
520
|
+
public_url, storage_key = storage.upload_file(
|
|
521
|
+
uploaded_file=request.FILES['avatar'],
|
|
522
|
+
key=f"users/{user_id}/avatar"
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
# Upload user avatar
|
|
526
|
+
public_url, key = storage.upload_user_avatar(user_id, uploaded_file)
|
|
527
|
+
|
|
528
|
+
# Upload workspace logo
|
|
529
|
+
public_url, key = storage.upload_workspace_logo(workspace_id, uploaded_file)
|
|
530
|
+
|
|
531
|
+
# Upload workspace media
|
|
532
|
+
public_url, key = storage.upload_workspace_media(workspace_id, uploaded_file)
|
|
533
|
+
|
|
534
|
+
# Delete file
|
|
535
|
+
storage.delete_by_key(storage_key)
|
|
536
|
+
|
|
537
|
+
# Upload from URL (e.g., social media image)
|
|
538
|
+
public_url, key = storage.upload_image_from_url(
|
|
539
|
+
image_url="https://example.com/image.jpg",
|
|
540
|
+
key=f"users/{user_id}/avatar"
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# Extract storage key from URL
|
|
544
|
+
storage_key = storage.key_from_url(public_url)
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**File Validation**:
|
|
548
|
+
```python
|
|
549
|
+
from core.services.object_storage import (
|
|
550
|
+
validate_uploaded_file,
|
|
551
|
+
IMAGE_MIME_TYPES,
|
|
552
|
+
AVATAR_MIME_TYPES
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# Validate file
|
|
556
|
+
error = validate_uploaded_file(
|
|
557
|
+
uploaded_file,
|
|
558
|
+
allowed_mime_types=IMAGE_MIME_TYPES,
|
|
559
|
+
max_size_bytes=5 * 1024 * 1024 # 5MB
|
|
560
|
+
)
|
|
561
|
+
if error:
|
|
562
|
+
return error_response(message=error, code=400)
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Environment Variables**:
|
|
566
|
+
```bash
|
|
567
|
+
R2_ACCOUNT_ID=your-account-id
|
|
568
|
+
R2_ACCESS_KEY_ID=your-access-key-id
|
|
569
|
+
R2_SECRET_ACCESS_KEY=your-secret-access-key
|
|
570
|
+
R2_BUCKET=your-bucket-name
|
|
571
|
+
R2_PUBLIC_BASE_URL=https://pub-xxx.r2.dev
|
|
572
|
+
R2_MEDIA_PATH=media
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### **Sentry Error Tracking Pattern**
|
|
576
|
+
|
|
577
|
+
**Purpose**: Automatic error tracking and performance monitoring.
|
|
578
|
+
|
|
579
|
+
**Configuration**: Sentry is automatically initialized when `SENTRY_DSN` is set.
|
|
580
|
+
|
|
581
|
+
```python
|
|
582
|
+
# Environment variable
|
|
583
|
+
SENTRY_DSN=https://xxx@sentry.io/xxx
|
|
584
|
+
|
|
585
|
+
# Sentry automatically captures:
|
|
586
|
+
# - Uncaught exceptions
|
|
587
|
+
# - All log levels (if enable_logs=True)
|
|
588
|
+
# - Performance traces (traces_sample_rate=1.0)
|
|
589
|
+
# - Profile data (profiles_sample_rate=1.0)
|
|
590
|
+
# - Request headers and IP (send_default_pii=True)
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
**Manual Error Reporting**:
|
|
594
|
+
```python
|
|
595
|
+
import sentry_sdk
|
|
596
|
+
|
|
597
|
+
# Capture exception
|
|
598
|
+
try:
|
|
599
|
+
risky_operation()
|
|
600
|
+
except Exception as e:
|
|
601
|
+
sentry_sdk.capture_exception(e)
|
|
602
|
+
raise
|
|
603
|
+
|
|
604
|
+
# Capture message
|
|
605
|
+
sentry_sdk.capture_message("Something important happened")
|
|
606
|
+
|
|
607
|
+
# Add context
|
|
608
|
+
with sentry_sdk.configure_scope() as scope:
|
|
609
|
+
scope.set_user({"id": user.id, "email": user.email})
|
|
610
|
+
scope.set_tag("environment", "production")
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## 🐳 Docker and Production Patterns
|
|
614
|
+
|
|
615
|
+
### **Docker Multi-Stage Build Pattern**
|
|
616
|
+
|
|
617
|
+
**Purpose**: Optimized production Docker builds with minimal image size.
|
|
618
|
+
|
|
619
|
+
```dockerfile
|
|
620
|
+
# Multi-stage build (already configured in Dockerfile)
|
|
621
|
+
FROM python:3.12-slim as builder
|
|
622
|
+
# Install build dependencies and compile packages
|
|
623
|
+
|
|
624
|
+
FROM python:3.12-slim as runtime
|
|
625
|
+
# Copy only compiled packages and application code
|
|
626
|
+
# Result: Smaller production image
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### **Nginx Reverse Proxy Pattern**
|
|
630
|
+
|
|
631
|
+
**Purpose**: Serve static files and proxy application requests.
|
|
632
|
+
|
|
633
|
+
```nginx
|
|
634
|
+
# config/http.conf (already configured)
|
|
635
|
+
server {
|
|
636
|
+
listen 80;
|
|
637
|
+
|
|
638
|
+
# WebSocket support
|
|
639
|
+
location /ws/ {
|
|
640
|
+
proxy_pass http://127.0.0.1:8000;
|
|
641
|
+
proxy_http_version 1.1;
|
|
642
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
643
|
+
proxy_set_header Connection "upgrade";
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
# Regular HTTP requests
|
|
647
|
+
location / {
|
|
648
|
+
proxy_pass http://127.0.0.1:8000;
|
|
649
|
+
proxy_set_header Host $host;
|
|
650
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### **Gunicorn + Uvicorn Workers Pattern**
|
|
656
|
+
|
|
657
|
+
**Purpose**: Production-grade ASGI server with multiple workers.
|
|
658
|
+
|
|
659
|
+
```python
|
|
660
|
+
# config/gunicorn_config.py (already configured)
|
|
661
|
+
command = '{project_name}.asgi:application'
|
|
662
|
+
pythonpath = '.'
|
|
663
|
+
bind = '0.0.0.0:8000'
|
|
664
|
+
workers = 4 # Auto-calculated based on CPU count
|
|
665
|
+
worker_class = 'uvicorn.workers.UvicornWorker'
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### **Entry Script Pattern**
|
|
669
|
+
|
|
670
|
+
**Purpose**: Graceful service startup and shutdown.
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
# .entry/start_core_backend.sh (already configured)
|
|
674
|
+
# Start nginx in background
|
|
675
|
+
nginx -c /app/config/http.conf
|
|
676
|
+
|
|
677
|
+
# Start gunicorn in background
|
|
678
|
+
gunicorn config.gunicorn_config &
|
|
679
|
+
|
|
680
|
+
# Handle graceful shutdown on SIGTERM/SIGINT
|
|
681
|
+
trap 'kill -TERM $NGINX_PID; kill -TERM $GUNICORN_PID' TERM INT
|
|
682
|
+
|
|
683
|
+
# Wait for processes
|
|
684
|
+
wait $GUNICORN_PID
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## 🔒 Production Configuration Pattern
|
|
688
|
+
|
|
689
|
+
### **Environment-Based Settings Pattern**
|
|
690
|
+
|
|
691
|
+
**Purpose**: Separate configurations for development and production.
|
|
692
|
+
|
|
693
|
+
```python
|
|
694
|
+
# settings/base.py (shared settings)
|
|
695
|
+
DEBUG = env.bool('DEBUG', default=False)
|
|
696
|
+
SENTRY_DSN = env('SENTRY_DSN', default='')
|
|
697
|
+
AZURE_COMMUNICATION_ENDPOINT = env('AZURE_COMMUNICATION_ENDPOINT', default='')
|
|
698
|
+
|
|
699
|
+
# settings/dev.py (development overrides)
|
|
700
|
+
DEBUG = True
|
|
701
|
+
|
|
702
|
+
# settings/prod.py (production overrides)
|
|
703
|
+
DEBUG = False
|
|
704
|
+
SESSION_COOKIE_SECURE = True
|
|
705
|
+
CSRF_COOKIE_SECURE = True
|
|
706
|
+
SECURE_SSL_REDIRECT = True
|
|
707
|
+
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### **ASGI/WSGI Production Settings Pattern**
|
|
711
|
+
|
|
712
|
+
**Purpose**: Use production settings in Docker environment.
|
|
713
|
+
|
|
714
|
+
```python
|
|
715
|
+
# asgi.py (uses prod settings)
|
|
716
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}.settings.prod')
|
|
717
|
+
|
|
718
|
+
# wsgi.py (uses prod settings)
|
|
719
|
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{project_name}.settings.prod')
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
This design pattern documentation ensures consistent, maintainable, and scalable code across the entire project. AI assistants should follow these patterns when working with the codebase.
|