from rest_framework import serializers from django.utils.html import strip_tags, escape from django.core.exceptions import ValidationError import re from .models import ContactSubmission class ContactSubmissionSerializer(serializers.ModelSerializer): """ Serializer for ContactSubmission model. Handles both creation and retrieval of contact form submissions. """ # Computed fields full_name = serializers.ReadOnlyField() is_high_priority = serializers.ReadOnlyField() is_enterprise_client = serializers.ReadOnlyField() industry_display = serializers.SerializerMethodField() project_type_display = serializers.SerializerMethodField() budget_display = serializers.SerializerMethodField() class Meta: model = ContactSubmission fields = [ 'id', 'first_name', 'last_name', 'full_name', 'email', 'phone', 'company', 'job_title', 'industry', 'industry_display', 'company_size', 'project_type', 'project_type_display', 'timeline', 'budget', 'budget_display', 'message', 'newsletter_subscription', 'privacy_consent', 'status', 'priority', 'is_high_priority', 'is_enterprise_client', 'created_at', 'updated_at', 'admin_notes', 'assigned_to', ] read_only_fields = [ 'id', 'status', 'priority', 'created_at', 'updated_at', 'admin_notes', 'assigned_to', ] def get_industry_display(self, obj): return obj.get_industry_display() def get_project_type_display(self, obj): return obj.get_project_type_display() def get_budget_display(self, obj): return obj.get_budget_display() def validate_email(self, value): """ Custom email validation to ensure it's a business email. """ if value and not any(domain in value.lower() for domain in ['@gmail.com', '@yahoo.com', '@hotmail.com']): return value # Allow personal emails but log them return value def validate_privacy_consent(self, value): """ Ensure privacy consent is given. """ if not value: raise serializers.ValidationError("Privacy consent is required to submit the form.") return value def validate(self, attrs): """ Cross-field validation. """ # Ensure required fields are present required_fields = ['first_name', 'last_name', 'email', 'company', 'job_title', 'message'] for field in required_fields: if not attrs.get(field): raise serializers.ValidationError(f"{field.replace('_', ' ').title()} is required.") # Validate enterprise client indicators if attrs.get('company_size') in ['201-1000', '1000+'] and attrs.get('budget') in ['under-50k', '50k-100k']: # This might be a mismatch, but we'll allow it and flag for review pass return attrs class ContactSubmissionCreateSerializer(serializers.ModelSerializer): """ Simplified serializer for creating contact submissions. Only includes fields that should be provided by the frontend. """ class Meta: model = ContactSubmission fields = [ 'first_name', 'last_name', 'email', 'phone', 'company', 'job_title', 'industry', 'company_size', 'project_type', 'timeline', 'budget', 'message', 'newsletter_subscription', 'privacy_consent', ] def _sanitize_text_field(self, value): """ Sanitize text fields by detecting and rejecting HTML/script tags. Returns cleaned text or raises ValidationError if dangerous content is detected. """ if not value: return value # Check for script tags and other dangerous HTML patterns dangerous_patterns = [ (r']*>.*?', 'Script tags are not allowed'), (r']*>.*?', 'Iframe tags are not allowed'), (r'javascript:', 'JavaScript protocol is not allowed'), (r'on\w+\s*=', 'Event handlers are not allowed'), (r']*onload', 'SVG onload handlers are not allowed'), (r']*onerror', 'Image onerror handlers are not allowed'), (r'<[^>]+>', 'HTML tags are not allowed'), # Catch any remaining HTML tags ] value_lower = value.lower() for pattern, message in dangerous_patterns: if re.search(pattern, value_lower, re.IGNORECASE | re.DOTALL): raise serializers.ValidationError( f"Invalid input detected: {message}. Please remove HTML tags and scripts." ) # Strip any remaining HTML tags (defense in depth) cleaned = strip_tags(value) # Remove any remaining script-like content cleaned = re.sub(r'javascript:', '', cleaned, flags=re.IGNORECASE) return cleaned.strip() def validate_first_name(self, value): """Sanitize first name field.""" return self._sanitize_text_field(value) def validate_last_name(self, value): """Sanitize last name field.""" return self._sanitize_text_field(value) def validate_company(self, value): """Sanitize company field.""" return self._sanitize_text_field(value) def validate_job_title(self, value): """Sanitize job title field.""" return self._sanitize_text_field(value) def validate_message(self, value): """Sanitize message field.""" return self._sanitize_text_field(value) def validate_phone(self, value): """Sanitize phone field - only allow alphanumeric, spaces, dashes, parentheses, and plus.""" if not value: return value # Remove HTML tags cleaned = strip_tags(value) # Only allow phone number characters if not re.match(r'^[\d\s\-\+\(\)]+$', cleaned): raise serializers.ValidationError("Phone number contains invalid characters.") return cleaned.strip() def validate_privacy_consent(self, value): """ Ensure privacy consent is given. """ if not value: raise serializers.ValidationError("Privacy consent is required to submit the form.") return value def validate(self, attrs): """ Cross-field validation for creation. """ # Ensure required fields are present required_fields = ['first_name', 'last_name', 'email', 'company', 'job_title', 'message'] for field in required_fields: if not attrs.get(field): raise serializers.ValidationError(f"{field.replace('_', ' ').title()} is required.") return attrs class ContactSubmissionListSerializer(serializers.ModelSerializer): """ Simplified serializer for listing contact submissions. Used in admin views and API listings. """ full_name = serializers.ReadOnlyField() is_high_priority = serializers.ReadOnlyField() is_enterprise_client = serializers.ReadOnlyField() class Meta: model = ContactSubmission fields = [ 'id', 'full_name', 'email', 'company', 'job_title', 'project_type', 'status', 'priority', 'is_high_priority', 'is_enterprise_client', 'created_at', ] class ContactSubmissionUpdateSerializer(serializers.ModelSerializer): """ Serializer for updating contact submissions (admin use). """ class Meta: model = ContactSubmission fields = [ 'status', 'priority', 'admin_notes', 'assigned_to', ] def validate_status(self, value): """ Validate status transitions. """ # Add business logic for status transitions if needed return value