🥈 Part 2: APIs বানানো – সবচেয়ে কম কোড দিয়ে, সহজে এবং ধাপে ধাপে
এখন আমরা Part 1-এর উপর ভিত্তি করে সব API বানাবো।
লক্ষ্য:
- কোড যতটা সম্ভব কম রাখা
- যেখানে পারি ModelViewSet + GenericAPIView ব্যবহার করা
- Serializers ও Views শুধু
campaign_app/api/ফোল্ডারে - Register, Login, Profile, Password Change/Reset, Photo Delete – সবই কভার
- Functionality তোমার আগের প্রজেক্টের মতোই থাকবে
Step 1: api/urls.py বানাও (App level routing)
campaign_app/api/urls.py
from django.urls import path
from .views import (
RegisterView,
LoginView,
ProfileView,
ProfileUpdateView,
ProfilePhotoDeleteView,
ChangePasswordView,
PasswordResetRequestView,
PasswordResetConfirmView,
)
urlpatterns = [
path('auth/register/', RegisterView.as_view(), name='register'),
path('auth/login/', LoginView.as_view(), name='login'),
path('auth/profile/', ProfileView.as_view(), name='profile-view'),
path('auth/profile/update/', ProfileUpdateView.as_view(), name='profile-update'),
path('auth/profile/photo-delete/', ProfilePhotoDeleteView.as_view(), name='photo-delete'),
path('auth/change-password/', ChangePasswordView.as_view(), name='change-password'),
path('auth/password-reset/', PasswordResetRequestView.as_view(), name='password-reset'),
path('auth/password-reset/<str:token>/', PasswordResetConfirmView.as_view(), name='password-reset-confirm'),
]কী হবে?
সব API /api/auth/... পাথে আসবে। Clean এবং organized।
Step 2: api/serializers.py
campaign_app/api/serializers.py
from rest_framework import serializers
from campaign_app.models import User, PasswordResetToken
from django.contrib.auth.password_validation import validate_password
from rest_framework_simplejwt.tokens import RefreshToken
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, validators=[validate_password])
confirm_password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = [
'email', 'password', 'confirm_password',
'business_owner_name', 'business_name',
'industry_type', 'mobile_number',
'address', 'address_line_2', 'city', 'state', 'zipcode'
]
def validate(self, attrs):
if attrs['password'] != attrs['confirm_password']:
raise serializers.ValidationError("Passwords do not match")
return attrs
def create(self, validated_data):
validated_data.pop('confirm_password')
user = User.objects.create_user(
email=validated_data['email'],
password=validated_data['password'],
**{k: v for k, v in validated_data.items() if k != 'email' and k != 'password'}
)
return user
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField()
password = serializers.CharField(write_only=True)
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
'id', 'email', 'business_owner_name', 'business_name',
'industry_type', 'mobile_number', 'address', 'address_line_2',
'city', 'state', 'zipcode', 'profile_photo', 'created_at'
]
read_only_fields = ['email', 'id', 'created_at']
class ChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True, validators=[validate_password])
confirm_password = serializers.CharField(required=True)
def validate(self, attrs):
if attrs['new_password'] != attrs['confirm_password']:
raise serializers.ValidationError("New passwords do not match")
return attrs
class PasswordResetRequestSerializer(serializers.Serializer):
email = serializers.EmailField()
class PasswordResetConfirmSerializer(serializers.Serializer):
new_password = serializers.CharField(validators=[validate_password])
confirm_password = serializers.CharField()ব্যাখ্যা:
- Register এ সব required + optional fields আছে
- Profile এ photo URL automatically আসবে (Django handle করে)
- Password validation Django এর built-in দিয়ে
Step 3: api/views.py – মেইন কাজ এখানে (কম কোড!)
campaign_app/api/views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import status, generics
from .serializers import *
from campaign_app.models import User, PasswordResetToken
from django.contrib.auth import authenticate
from rest_framework_simplejwt.tokens import RefreshToken
from django.core.mail import send_mail
from django.utils import timezone
from datetime import timedelta
import uuid
import os
# Helper: JWT Token generate
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class RegisterView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
token = get_tokens_for_user(user)
return Response({
'success': True,
'message': 'Registration successful',
'token': token
}, status=status.HTTP_201_CREATED)
return Response({
'success': False,
'errors': serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
class LoginView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
email = serializer.validated_data['email'].lower()
password = serializer.validated_data['password']
user = authenticate(email=email, password=password)
if user:
token = get_tokens_for_user(user)
profile_serializer = ProfileSerializer(user)
return Response({
'success': True,
'token': token,
'user': profile_serializer.data
})
return Response({
'success': False,
'message': 'Invalid credentials'
}, status=status.HTTP_401_UNAUTHORIZED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ProfileView(generics.RetrieveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = ProfileSerializer
def get_object(self):
return self.request.user
class ProfileUpdateView(generics.UpdateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = ProfileSerializer
def get_object(self):
return self.request.user
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', True)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response({
'success': True,
'message': 'Profile updated',
'data': serializer.data
})
class ProfilePhotoDeleteView(APIView):
permission_classes = [IsAuthenticated]
def delete(self, request):
user = request.user
if user.profile_photo:
user.profile_photo.delete()
user.save()
return Response({'success': True, 'message': 'Photo deleted'})
return Response({'success': False, 'message': 'No photo to delete'}, status=400)
class ChangePasswordView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ChangePasswordSerializer(data=request.data)
if serializer.is_valid():
user = request.user
if not user.check_password(serializer.validated_data['old_password']):
return Response({'message': 'Old password is incorrect'}, status=400)
user.set_password(serializer.validated_data['new_password'])
user.save()
return Response({'success': True, 'message': 'Password changed successfully'})
return Response(serializer.errors, status=400)
class PasswordResetRequestView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = PasswordResetRequestSerializer(data=request.data)
if serializer.is_valid():
email = serializer.validated_data['email'].lower()
user = User.objects.filter(email=email).first()
if user:
token = uuid.uuid4()
PasswordResetToken.objects.update_or_create(
user=user,
defaults={'token': token, 'created_at': timezone.now()}
)
reset_url = f"https://your-frontend.com/reset-password/{token}" # তোমার frontend URL দাও
send_mail(
'Password Reset Request',
f'Click here to reset: {reset_url}',
os.getenv('EMAIL_HOST_USER'),
[email],
fail_silently=False,
)
return Response({'success': True, 'message': 'Reset link sent if email exists'})
return Response(serializer.errors, status=400)
class PasswordResetConfirmView(APIView):
permission_classes = [AllowAny]
def post(self, request, token):
serializer = PasswordResetConfirmSerializer(data=request.data)
if serializer.is_valid():
try:
reset_token = PasswordResetToken.objects.get(token=token)
if (timezone.now() - reset_token.created_at) > timedelta(hours=1):
raise Exception("Expired")
user = reset_token.user
if serializer.validated_data['new_password'] != serializer.validated_data['confirm_password']:
return Response({'message': 'Passwords do not match'}, status=400)
user.set_password(serializer.validated_data['new_password'])
user.save()
reset_token.delete()
return Response({'success': True, 'message': 'Password reset successful'})
except PasswordResetToken.DoesNotExist:
return Response({'message': 'Invalid token'}, status=400)
except:
return Response({'message': 'Expired or invalid token'}, status=400)
return Response(serializer.errors, status=400)Step 4: Migration & Run
python manage.py makemigrations
python manage.py migrate
python manage.py runserverAPI List (সংক্ষেপে)
| Endpoint | Method | Auth? | Purpose |
|---|---|---|---|
/api/auth/register/ | POST | No | Registration + JWT |
/api/auth/login/ | POST | No | Login + JWT + Profile |
/api/auth/profile/ | GET | Yes | View profile |
/api/auth/profile/update/ | PUT/PATCH | Yes | Update profile + photo |
/api/auth/profile/photo-delete/ | DELETE | Yes | Delete photo |
/api/auth/change-password/ | POST | Yes | Change password |
/api/auth/password-reset/ | POST | No | Send reset email |
/api/auth/password-reset/<token>/ | POST | No | Confirm new password |