diff --git a/employer_recommendation_system/employer_recommendation_system/__init__.py b/employer_recommendation_system/employer_recommendation_system/__init__.py index e69de29..9e0d95f 100644 --- a/employer_recommendation_system/employer_recommendation_system/__init__.py +++ b/employer_recommendation_system/employer_recommendation_system/__init__.py @@ -0,0 +1,3 @@ +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/employer_recommendation_system/employer_recommendation_system/celery.py b/employer_recommendation_system/employer_recommendation_system/celery.py new file mode 100644 index 0000000..89406c5 --- /dev/null +++ b/employer_recommendation_system/employer_recommendation_system/celery.py @@ -0,0 +1,8 @@ +import os +from celery import Celery + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'employer_recommendation_system.settings') + +app = Celery('employer_recommendation_system') +app.config_from_object('django.conf:settings', namespace='CELERY') +app.autodiscover_tasks() diff --git a/employer_recommendation_system/employer_recommendation_system/settings.py b/employer_recommendation_system/employer_recommendation_system/settings.py index 274d822..88b02f8 100644 --- a/employer_recommendation_system/employer_recommendation_system/settings.py +++ b/employer_recommendation_system/employer_recommendation_system/settings.py @@ -261,4 +261,25 @@ # global settings/configuration for a Simple JWT SIMPLE_JWT = { "TOKEN_OBTAIN_SERIALIZER": "auth.serializers.CustomTokenObtainPairSerializer", -} \ No newline at end of file +} +# EMAIL_CONFIG is a dictionary in config.py +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = EMAIL_HOST +EMAIL_PORT = EMAIL_PORT +EMAIL_USE_TLS = EMAIL_USE_TLS +EMAIL_USE_SSL = EMAIL_USE_SSL +EMAIL_HOST_USER = EMAIL_HOST_USER +EMAIL_HOST_PASSWORD = EMAIL_HOST_PASSWORD + + +# ------------------------------------------------------- +# 14. CELERY + RABBITMQ SETTINGS (loaded from config.py) +# ------------------------------------------------------- +CELERY_BROKER_URL ='amqp://guest:guest@localhost:5672//' +#CELERY_RESULT_BACKEND = 'CELERY_RESULT_BACKEND', 'rpc://') +CELERY_ACCEPT_CONTENT = ['json'] +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_TIMEZONE = 'Asia/Kolkata' +CELERY_TASK_TRACK_STARTED = True # Tracks when a task is started +CELERY_TASK_TIME_LIMIT = 30 * 60 # 30 min hard time limit diff --git a/employer_recommendation_system/mailer/admin.py b/employer_recommendation_system/mailer/admin.py index 8c38f3f..a9fe791 100644 --- a/employer_recommendation_system/mailer/admin.py +++ b/employer_recommendation_system/mailer/admin.py @@ -1,3 +1,16 @@ from django.contrib import admin # Register your models here. +from .models import EmailRecord, EmailContent + +@admin.register(EmailRecord) +class EmailRecordAdmin(admin.ModelAdmin): + list_display = ('email_address', 'status', 'sent_at', 'error_message') + list_filter = ('status',) + search_fields = ('email_address',) + + +@admin.register(EmailContent) +class EmailContentAdmin(admin.ModelAdmin): + list_display = ('subject', 'created_at', 'user_id') + filter_horizontal = ('email_records',) diff --git a/employer_recommendation_system/mailer/models.py b/employer_recommendation_system/mailer/models.py index 71a8362..8414cf3 100644 --- a/employer_recommendation_system/mailer/models.py +++ b/employer_recommendation_system/mailer/models.py @@ -1,3 +1,52 @@ from django.db import models +from django.contrib.auth.models import User # Create your models here. + + +class EmailRecord(models.Model): + """ + Stores individual email addresses and their sending status. + Each row corresponds to one email address from the uploaded CSV file. + """ + email_address = models.EmailField() + created_at = models.DateTimeField(auto_now_add=True) + status = models.CharField( + max_length=20, + choices=[ + ('pending', 'Pending'), + ('sent', 'Sent'), + ('failed', 'Failed'), + ], + default='pending' + ) + sent_at = models.DateTimeField(null=True, blank=True) + error_message = models.TextField(null=True, blank=True) + + class Meta: + indexes = [models.Index(fields=['email_address'])] + + def __str__(self): + return f"{self.email_address} - {self.status}" + + +class EmailContent(models.Model): + """ + Stores the common mail subject and body for a group of email addresses. + One EmailContent can be linked to multiple EmailRecords. + """ + subject = models.CharField(max_length=255) + mail_body = models.TextField() + email_records = models.ManyToManyField( + EmailRecord, + related_name='email_contents' + ) + created_at = models.DateTimeField(auto_now_add=True) + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + related_name='email_contents' + ) + + def __str__(self): + return f"EmailContent (Subject: {self.subject})" diff --git a/employer_recommendation_system/mailer/tasks.py b/employer_recommendation_system/mailer/tasks.py new file mode 100644 index 0000000..d58fae4 --- /dev/null +++ b/employer_recommendation_system/mailer/tasks.py @@ -0,0 +1,36 @@ +from celery import shared_task +from django.utils import timezone +from django.core.mail import send_mail +from .models import EmailRecord, EmailContent + + +@shared_task(bind=True, max_retries=3, default_retry_delay=60) +def send_bulk_emails(self, email_content_id): + """ + Celery task to send bulk emails based on EmailContent and linked EmailRecords. + """ + try: + content = EmailContent.objects.get(pk=email_content_id) + records = content.email_records.all() + + for record in records: + try: + send_mail( + subject=content.subject, + message=content.mail_body, + from_email=None, + recipient_list=[record.email_address], + fail_silently=False, + ) + record.status = 'sent' + record.sent_at = timezone.now() + record.save() + except Exception as e: + record.status = 'failed' + record.error_message = str(e) + record.save() + + return f"Bulk email task completed for EmailContent ID {email_content_id}" + + except Exception as exc: + raise self.retry(exc=exc) diff --git a/employer_recommendation_system/mailer/utils.py b/employer_recommendation_system/mailer/utils.py new file mode 100644 index 0000000..8af95ab --- /dev/null +++ b/employer_recommendation_system/mailer/utils.py @@ -0,0 +1,16 @@ +import csv +from .models import EmailRecord + +def import_emails_from_csv(csv_file): + """ + Reads a CSV containing email addresses and returns a list of EmailRecord objects. + CSV must have a column named 'email'. + """ + records = [] + reader = csv.DictReader(csv_file.read().decode('utf-8').splitlines()) + for row in reader: + email = row.get('email') + if email: + record, _ = EmailRecord.objects.get_or_create(email_address=email) + records.append(record) + return records diff --git a/employer_recommendation_system/mailer/views.py b/employer_recommendation_system/mailer/views.py index 91ea44a..6bd6d16 100644 --- a/employer_recommendation_system/mailer/views.py +++ b/employer_recommendation_system/mailer/views.py @@ -1,3 +1,16 @@ from django.shortcuts import render # Create your views here. +from django.shortcuts import redirect +from django.contrib import messages +from .models import EmailContent +from .tasks import send_bulk_emails + +def trigger_bulk_mail(request, content_id): + """ + Trigger Celery task to send emails asynchronously for a given EmailContent. + """ + content = EmailContent.objects.get(pk=content_id) + send_bulk_emails.delay(content.id) + messages.success(request, f"Email sending started for '{content.subject}'") + return redirect('admin:mailer_emailcontent_changelist')