Spaces:
Sleeping
Sleeping
| """ | |
| DRF Serializers β validation, representation, and nested relations. | |
| """ | |
| from django.contrib.auth import get_user_model | |
| from django.utils import timezone | |
| from rest_framework import serializers | |
| from rest_framework_simplejwt.serializers import TokenObtainPairSerializer | |
| from api.models import ( | |
| Caregiver, CaregiverService, Pet, Service, | |
| Booking, BookingStatus, Conversation, Message, Role, | |
| ) | |
| User = get_user_model() | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # JWT Custom Claims | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class CustomTokenObtainPairSerializer(TokenObtainPairSerializer): | |
| def get_token(cls, user): | |
| token = super().get_token(user) | |
| token['email'] = user.email | |
| token['role'] = user.role | |
| token['full_name'] = user.full_name | |
| return token | |
| def validate(self, attrs): | |
| data = super().validate(attrs) | |
| data['user'] = { | |
| 'id': str(self.user.id), | |
| 'email': self.user.email, | |
| 'role': self.user.role, | |
| 'full_name': self.user.full_name, | |
| 'avatar_url': self.user.avatar_url, | |
| } | |
| return data | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Auth Serializers | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class RegisterSerializer(serializers.ModelSerializer): | |
| password = serializers.CharField(write_only=True, min_length=10) | |
| password_confirm = serializers.CharField(write_only=True) | |
| class Meta: | |
| model = User | |
| fields = ['email', 'first_name', 'last_name', 'password', 'password_confirm'] | |
| def validate(self, data): | |
| if data['password'] != data['password_confirm']: | |
| raise serializers.ValidationError({'password_confirm': 'Passwords do not match.'}) | |
| return data | |
| def create(self, validated_data): | |
| validated_data.pop('password_confirm') | |
| return User.objects.create_user( | |
| role=Role.USER, | |
| **validated_data, | |
| ) | |
| class UserProfileSerializer(serializers.ModelSerializer): | |
| full_name = serializers.ReadOnlyField() | |
| class Meta: | |
| model = User | |
| fields = [ | |
| 'id', 'email', 'first_name', 'last_name', 'full_name', | |
| 'role', 'phone', 'address', 'avatar_url', 'date_joined', | |
| ] | |
| read_only_fields = ['id', 'role', 'date_joined'] | |
| class UserPublicSerializer(serializers.ModelSerializer): | |
| """Minimal public view β no PII.""" | |
| full_name = serializers.ReadOnlyField() | |
| class Meta: | |
| model = User | |
| fields = ['id', 'full_name', 'avatar_url'] | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Service | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ServiceSerializer(serializers.ModelSerializer): | |
| class Meta: | |
| model = Service | |
| fields = '__all__' | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Caregiver | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class CaregiverServiceSerializer(serializers.ModelSerializer): | |
| service_name = serializers.ReadOnlyField(source='service.name') | |
| service_icon = serializers.ReadOnlyField(source='service.icon') | |
| class Meta: | |
| model = CaregiverService | |
| fields = ['service', 'service_name', 'service_icon', 'price_per_hour', 'is_active'] | |
| class CaregiverListSerializer(serializers.ModelSerializer): | |
| """Lightweight serializer for list views / search.""" | |
| full_name = serializers.ReadOnlyField(source='user.full_name') | |
| avatar_url = serializers.ReadOnlyField(source='user.avatar_url') | |
| class Meta: | |
| model = Caregiver | |
| fields = [ | |
| 'id', 'full_name', 'avatar_url', 'profile_image_url', | |
| 'city', 'country', 'rating', 'total_reviews', 'total_bookings', | |
| 'years_of_experience', 'specializations', 'is_available', | |
| 'is_verified', 'is_featured', | |
| ] | |
| class CaregiverDetailSerializer(serializers.ModelSerializer): | |
| """Full detail including services and user profile.""" | |
| full_name = serializers.ReadOnlyField(source='user.full_name') | |
| email = serializers.ReadOnlyField(source='user.email') | |
| avatar_url = serializers.ReadOnlyField(source='user.avatar_url') | |
| caregiver_services = CaregiverServiceSerializer( | |
| source='caregiverservice_set', many=True, read_only=True | |
| ) | |
| class Meta: | |
| model = Caregiver | |
| fields = [ | |
| 'id', 'full_name', 'email', 'avatar_url', | |
| 'profile_image_url', 'gallery_images', 'bio', | |
| 'years_of_experience', 'specializations', 'certifications', 'languages', | |
| 'city', 'country', 'latitude', 'longitude', | |
| 'rating', 'total_reviews', 'total_bookings', | |
| 'is_available', 'is_verified', 'is_featured', 'background_check_passed', | |
| 'caregiver_services', 'created_at', | |
| ] | |
| read_only_fields = ['rating', 'total_reviews', 'total_bookings', 'created_at'] | |
| class CaregiverWriteSerializer(serializers.ModelSerializer): | |
| """Admin-only β create/update caregiver profiles.""" | |
| email = serializers.EmailField(write_only=True) | |
| first_name = serializers.CharField(write_only=True, max_length=100) | |
| last_name = serializers.CharField(write_only=True, max_length=100) | |
| password = serializers.CharField(write_only=True, min_length=10, required=False) | |
| class Meta: | |
| model = Caregiver | |
| fields = [ | |
| 'email', 'first_name', 'last_name', 'password', | |
| 'bio', 'years_of_experience', 'specializations', 'certifications', | |
| 'languages', 'city', 'country', 'latitude', 'longitude', | |
| 'is_available', 'is_verified', 'is_featured', 'background_check_passed', | |
| 'emergency_contact', | |
| ] | |
| def create(self, validated_data): | |
| email = validated_data.pop('email') | |
| first_name = validated_data.pop('first_name') | |
| last_name = validated_data.pop('last_name') | |
| password = validated_data.pop('password', User.objects.make_random_password(16)) | |
| user = User.objects.create_user( | |
| email=email, | |
| first_name=first_name, | |
| last_name=last_name, | |
| password=password, | |
| role=Role.CAREGIVER, | |
| ) | |
| return Caregiver.objects.create(user=user, **validated_data) | |
| def update(self, instance, validated_data): | |
| # Update nested user fields if provided | |
| for field in ('email', 'first_name', 'last_name'): | |
| val = validated_data.pop(field, None) | |
| if val: | |
| setattr(instance.user, field, val) | |
| password = validated_data.pop('password', None) | |
| if password: | |
| instance.user.set_password(password) | |
| instance.user.save() | |
| for attr, val in validated_data.items(): | |
| setattr(instance, attr, val) | |
| instance.save() | |
| return instance | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Pet | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class PetSerializer(serializers.ModelSerializer): | |
| class Meta: | |
| model = Pet | |
| fields = '__all__' | |
| read_only_fields = ['id', 'owner', 'created_at', 'updated_at'] | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Booking | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class BookingCreateSerializer(serializers.ModelSerializer): | |
| class Meta: | |
| model = Booking | |
| fields = [ | |
| 'caregiver', 'pet', 'service', | |
| 'scheduled_start', 'scheduled_end', | |
| 'notes', 'service_address', | |
| ] | |
| def validate(self, data): | |
| if data['scheduled_end'] <= data['scheduled_start']: | |
| raise serializers.ValidationError('End time must be after start time.') | |
| # Ensure pet belongs to the requesting user | |
| request = self.context['request'] | |
| if data['pet'].owner != request.user: | |
| raise serializers.ValidationError({'pet': 'You can only book for your own pets.'}) | |
| return data | |
| def create(self, validated_data): | |
| user = self.context['request'].user | |
| service = validated_data['service'] | |
| caregiver = validated_data['caregiver'] | |
| # Calculate pricing | |
| hours = ( | |
| validated_data['scheduled_end'] - validated_data['scheduled_start'] | |
| ).total_seconds() / 3600 | |
| try: | |
| cs = CaregiverService.objects.get( | |
| caregiver=caregiver, service=service, is_active=True | |
| ) | |
| subtotal = cs.price_per_hour * hours | |
| except CaregiverService.DoesNotExist: | |
| subtotal = service.base_price * hours | |
| fees = subtotal * 0.05 # 5% platform fee | |
| total = subtotal + fees | |
| return Booking.objects.create( | |
| user=user, | |
| price_subtotal=round(subtotal, 2), | |
| price_fees=round(fees, 2), | |
| price_total=round(total, 2), | |
| **validated_data, | |
| ) | |
| class BookingSerializer(serializers.ModelSerializer): | |
| user = UserPublicSerializer(read_only=True) | |
| caregiver = CaregiverListSerializer(read_only=True) | |
| service = ServiceSerializer(read_only=True) | |
| pet = PetSerializer(read_only=True) | |
| class Meta: | |
| model = Booking | |
| fields = '__all__' | |
| read_only_fields = [ | |
| 'id', 'user', 'price_subtotal', 'price_fees', 'price_total', | |
| 'created_at', 'updated_at', | |
| ] | |
| class BookingStatusUpdateSerializer(serializers.Serializer): | |
| status = serializers.ChoiceField(choices=BookingStatus.choices) | |
| cancellation_reason = serializers.CharField(required=False, allow_blank=True) | |
| class BookingReviewSerializer(serializers.Serializer): | |
| rating = serializers.IntegerField(min_value=1, max_value=5) | |
| review = serializers.CharField(max_length=1000, allow_blank=True) | |
| def update(self, instance, validated_data): | |
| instance.rating = validated_data['rating'] | |
| instance.review = validated_data.get('review', '') | |
| instance.reviewed_at = timezone.now() | |
| instance.save() | |
| # Update caregiver aggregate rating | |
| caregiver = instance.caregiver | |
| bookings_with_reviews = Booking.objects.filter( | |
| caregiver=caregiver, rating__isnull=False | |
| ) | |
| count = bookings_with_reviews.count() | |
| if count: | |
| avg = sum(b.rating for b in bookings_with_reviews) / count | |
| caregiver.rating = round(avg, 2) | |
| caregiver.total_reviews = count | |
| caregiver.save(update_fields=['rating', 'total_reviews']) | |
| return instance | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Messaging | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MessageSerializer(serializers.ModelSerializer): | |
| sender = UserPublicSerializer(read_only=True) | |
| class Meta: | |
| model = Message | |
| fields = [ | |
| 'id', 'conversation', 'sender', 'message_type', | |
| 'content', 'image_url', 'is_read', 'read_at', 'created_at', | |
| ] | |
| read_only_fields = ['id', 'sender', 'is_read', 'read_at', 'created_at'] | |
| class ConversationSerializer(serializers.ModelSerializer): | |
| participants = UserPublicSerializer(many=True, read_only=True) | |
| last_message = serializers.SerializerMethodField() | |
| unread_count = serializers.SerializerMethodField() | |
| class Meta: | |
| model = Conversation | |
| fields = [ | |
| 'id', 'participants', 'booking', 'last_message', | |
| 'unread_count', 'created_at', 'updated_at', | |
| ] | |
| def get_last_message(self, obj): | |
| msg = obj.messages.last() | |
| if msg: | |
| return {'content': msg.content[:80], 'created_at': msg.created_at} | |
| return None | |
| def get_unread_count(self, obj): | |
| user = self.context['request'].user | |
| return obj.messages.filter(is_read=False).exclude(sender=user).count() | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Image Upload | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ImageUploadSerializer(serializers.Serializer): | |
| image = serializers.ImageField() | |
| folder = serializers.ChoiceField( | |
| choices=['caregivers', 'pets', 'messages'], | |
| default='caregivers', | |
| ) | |