Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .celery import app as celery_app

__all__ = ('celery_app',)
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,25 @@
# global settings/configuration for a Simple JWT
SIMPLE_JWT = {
"TOKEN_OBTAIN_SERIALIZER": "auth.serializers.CustomTokenObtainPairSerializer",
}
}
# 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
13 changes: 13 additions & 0 deletions employer_recommendation_system/mailer/admin.py
Original file line number Diff line number Diff line change
@@ -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',)
49 changes: 49 additions & 0 deletions employer_recommendation_system/mailer/models.py
Original file line number Diff line number Diff line change
@@ -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'
)
Comment on lines 14 to 22
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define choices in tuple, and then use it in choices.
STATUS_CHOICES = [
('pending', 'Pending'),
('sent', 'Sent'),
('failed', 'Failed'),
]

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})"
36 changes: 36 additions & 0 deletions employer_recommendation_system/mailer/tasks.py
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 16 additions & 0 deletions employer_recommendation_system/mailer/utils.py
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions employer_recommendation_system/mailer/views.py
Original file line number Diff line number Diff line change
@@ -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')