This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

405
emails/views.py Normal file
View File

@@ -0,0 +1,405 @@
from rest_framework import generics, status, permissions, filters
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.views import APIView
from django_filters.rest_framework import DjangoFilterBackend
from django.db.models import Q, Count
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django_ratelimit.decorators import ratelimit
import smtplib
import imaplib
import email
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
from .models import (
Email, EmailFolder, EmailAttachment, EmailThread, EmailTemplate,
EmailSignature, EmailRule, EmailSearch
)
from .serializers import (
EmailSerializer, EmailCreateSerializer, EmailReplySerializer, EmailForwardSerializer,
EmailFolderSerializer, EmailAttachmentSerializer, EmailThreadSerializer,
EmailTemplateSerializer, EmailSignatureSerializer, EmailRuleSerializer,
EmailSearchSerializer, EmailBulkActionSerializer
)
from .tasks import send_email_task, fetch_emails_task
class EmailFolderListCreateView(generics.ListCreateAPIView):
"""List and create email folders."""
serializer_class = EmailFolderSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailFolder.objects.filter(user=self.request.user)
class EmailFolderDetailView(generics.RetrieveUpdateDestroyAPIView):
"""Retrieve, update, or delete email folder."""
serializer_class = EmailFolderSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailFolder.objects.filter(user=self.request.user)
class EmailListCreateView(generics.ListCreateAPIView):
"""List and create emails."""
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['folder', 'is_read', 'is_starred', 'is_important', 'status']
search_fields = ['subject', 'from_email', 'body_text']
ordering_fields = ['created_at', 'sent_at', 'subject']
ordering = ['-created_at']
def get_serializer_class(self):
if self.request.method == 'POST':
return EmailCreateSerializer
return EmailSerializer
def get_queryset(self):
return Email.objects.filter(user=self.request.user).select_related('folder').prefetch_related('attachments')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailDetailView(generics.RetrieveUpdateDestroyAPIView):
"""Retrieve, update, or delete email."""
serializer_class = EmailSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Email.objects.filter(user=self.request.user).select_related('folder').prefetch_related('attachments')
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
# Mark as read when retrieved
if not instance.is_read:
instance.mark_as_read()
serializer = self.get_serializer(instance)
return Response(serializer.data)
class EmailReplyView(generics.CreateAPIView):
"""Reply to an email."""
serializer_class = EmailReplySerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailForwardView(generics.CreateAPIView):
"""Forward an email."""
serializer_class = EmailForwardSerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailBulkActionView(APIView):
"""Perform bulk actions on emails."""
permission_classes = [permissions.IsAuthenticated]
def post(self, request):
serializer = EmailBulkActionSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
email_ids = serializer.validated_data['email_ids']
action = serializer.validated_data['action']
folder_id = serializer.validated_data.get('folder_id')
emails = Email.objects.filter(id__in=email_ids, user=request.user)
if action == 'mark_read':
emails.update(is_read=True)
elif action == 'mark_unread':
emails.update(is_read=False)
elif action == 'mark_starred':
emails.update(is_starred=True)
elif action == 'mark_unstarred':
emails.update(is_starred=False)
elif action == 'mark_important':
emails.update(is_important=True)
elif action == 'mark_unimportant':
emails.update(is_important=False)
elif action == 'move_to_folder':
folder = get_object_or_404(EmailFolder, id=folder_id, user=request.user)
emails.update(folder=folder)
elif action == 'delete':
emails.delete()
return Response({'message': f'Bulk action {action} completed successfully'})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class EmailSearchView(generics.ListAPIView):
"""Search emails with advanced filters."""
serializer_class = EmailSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
queryset = Email.objects.filter(user=self.request.user)
# Get search parameters
query = self.request.query_params.get('q', '')
folder = self.request.query_params.get('folder')
is_read = self.request.query_params.get('is_read')
is_starred = self.request.query_params.get('is_starred')
is_important = self.request.query_params.get('is_important')
date_from = self.request.query_params.get('date_from')
date_to = self.request.query_params.get('date_to')
# Apply filters
if query:
queryset = queryset.filter(
Q(subject__icontains=query) |
Q(from_email__icontains=query) |
Q(body_text__icontains=query)
)
if folder:
queryset = queryset.filter(folder_id=folder)
if is_read is not None:
queryset = queryset.filter(is_read=is_read.lower() == 'true')
if is_starred is not None:
queryset = queryset.filter(is_starred=is_starred.lower() == 'true')
if is_important is not None:
queryset = queryset.filter(is_important=is_important.lower() == 'true')
if date_from:
queryset = queryset.filter(created_at__gte=date_from)
if date_to:
queryset = queryset.filter(created_at__lte=date_to)
return queryset.select_related('folder').prefetch_related('attachments')
class EmailThreadView(generics.RetrieveAPIView):
"""Get email thread."""
serializer_class = EmailThreadSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailThread.objects.filter(emails__user=self.request.user).distinct()
class EmailTemplateListCreateView(generics.ListCreateAPIView):
"""List and create email templates."""
serializer_class = EmailTemplateSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailTemplate.objects.filter(
Q(user=self.request.user) | Q(is_public=True)
).order_by('name')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailTemplateDetailView(generics.RetrieveUpdateDestroyAPIView):
"""Retrieve, update, or delete email template."""
serializer_class = EmailTemplateSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailTemplate.objects.filter(user=self.request.user)
class EmailSignatureListCreateView(generics.ListCreateAPIView):
"""List and create email signatures."""
serializer_class = EmailSignatureSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailSignature.objects.filter(user=self.request.user).order_by('name')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailSignatureDetailView(generics.RetrieveUpdateDestroyAPIView):
"""Retrieve, update, or delete email signature."""
serializer_class = EmailSignatureSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailSignature.objects.filter(user=self.request.user)
class EmailRuleListCreateView(generics.ListCreateAPIView):
"""List and create email rules."""
serializer_class = EmailRuleSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailRule.objects.filter(user=self.request.user).order_by('name')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailRuleDetailView(generics.RetrieveUpdateDestroyAPIView):
"""Retrieve, update, or delete email rule."""
serializer_class = EmailRuleSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailRule.objects.filter(user=self.request.user)
class EmailSearchListCreateView(generics.ListCreateAPIView):
"""List and create saved email searches."""
serializer_class = EmailSearchSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailSearch.objects.filter(user=self.request.user).order_by('name')
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class EmailSearchDetailView(generics.RetrieveUpdateDestroyAPIView):
"""Retrieve, update, or delete saved email search."""
serializer_class = EmailSearchSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return EmailSearch.objects.filter(user=self.request.user)
@api_view(['POST'])
@permission_classes([permissions.IsAuthenticated])
@ratelimit(key='user', rate='10/m', method=['POST'])
def send_email(request):
"""Send email endpoint."""
serializer = EmailCreateSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
email = serializer.save()
# Send email asynchronously
send_email_task.delay(email.id)
return Response({
'message': 'Email queued for sending',
'email': EmailSerializer(email).data
}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([permissions.IsAuthenticated])
@ratelimit(key='user', rate='5/m', method=['POST'])
def fetch_emails(request):
"""Fetch emails from IMAP server."""
user = request.user
# Check if user has IMAP settings configured
if not user.imap_host or not user.imap_username:
return Response(
{'error': 'IMAP settings not configured'},
status=status.HTTP_400_BAD_REQUEST
)
# Fetch emails asynchronously
fetch_emails_task.delay(user.id)
return Response({'message': 'Email fetch started'}, status=status.HTTP_200_OK)
@api_view(['GET'])
@permission_classes([permissions.IsAuthenticated])
def email_stats(request):
"""Get email statistics."""
user = request.user
stats = {
'total_emails': Email.objects.filter(user=user).count(),
'unread_emails': Email.objects.filter(user=user, is_read=False).count(),
'starred_emails': Email.objects.filter(user=user, is_starred=True).count(),
'important_emails': Email.objects.filter(user=user, is_important=True).count(),
'sent_emails': Email.objects.filter(user=user, status='sent').count(),
'draft_emails': Email.objects.filter(user=user, status='draft').count(),
'folder_stats': EmailFolder.objects.filter(user=user).annotate(
email_count=Count('emails'),
unread_count=Count('emails', filter=Q(emails__is_read=False))
).values('name', 'email_count', 'unread_count'),
}
return Response(stats)
@api_view(['POST'])
@permission_classes([permissions.IsAuthenticated])
def test_email_settings(request):
"""Test email server settings."""
user = request.user
settings_type = request.data.get('type', 'smtp') # smtp or imap
try:
if settings_type == 'smtp':
# Test SMTP connection
if not user.smtp_host or not user.smtp_username:
return Response(
{'error': 'SMTP settings not configured'},
status=status.HTTP_400_BAD_REQUEST
)
server = smtplib.SMTP(user.smtp_host, user.smtp_port)
if user.smtp_use_tls:
server.starttls()
server.login(user.smtp_username, user.get_smtp_password())
server.quit()
elif settings_type == 'imap':
# Test IMAP connection
if not user.imap_host or not user.imap_username:
return Response(
{'error': 'IMAP settings not configured'},
status=status.HTTP_400_BAD_REQUEST
)
if user.imap_use_ssl:
server = imaplib.IMAP4_SSL(user.imap_host, user.imap_port)
else:
server = imaplib.IMAP4(user.imap_host, user.imap_port)
server.login(user.imap_username, user.get_imap_password())
server.logout()
return Response({'message': f'{settings_type.upper()} connection successful'})
except Exception as e:
return Response(
{'error': f'{settings_type.upper()} connection failed: {str(e)}'},
status=status.HTTP_400_BAD_REQUEST
)