Files
GNX-WEB/backEnd/contact/serializers.py
Iliyan Angelov 6a9e823402 updates
2025-12-10 01:36:00 +02:00

266 lines
8.4 KiB
Python

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[^>]*>.*?</script>', 'Script tags are not allowed'),
(r'<iframe[^>]*>.*?</iframe>', 'Iframe tags are not allowed'),
(r'javascript:', 'JavaScript protocol is not allowed'),
(r'on\w+\s*=', 'Event handlers are not allowed'),
(r'<svg[^>]*onload', 'SVG onload handlers are not allowed'),
(r'<img[^>]*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