1414from django .conf import settings
1515from django .core import serializers
1616from django .core .files .temp import NamedTemporaryFile
17+ from django .core .mail import EmailMultiAlternatives
18+ from django .template .loader import render_to_string
19+ from django .utils .html import strip_tags
1720
1821from evalai .celery import app
1922
@@ -2309,6 +2312,103 @@ def update_submission_retention_dates():
23092312 return {"updated_submissions" : updated_count , "errors" : errors }
23102313
23112314
2315+ def send_template_email (
2316+ recipient_email ,
2317+ subject ,
2318+ template_name ,
2319+ template_context ,
2320+ sender_email = None ,
2321+ reply_to = None ,
2322+ ):
2323+ """
2324+ Send an email using Django templates instead of SendGrid.
2325+
2326+ Args:
2327+ recipient_email (str): Email address of the recipient
2328+ subject (str): Email subject line
2329+ template_name (str): Template name (e.g., 'challenges/retention_warning.html')
2330+ template_context (dict): Context data for the template
2331+ sender_email (str, optional): Sender email address. Defaults to CLOUDCV_TEAM_EMAIL
2332+ reply_to (str, optional): Reply-to email address
2333+
2334+ Returns:
2335+ bool: True if email was sent successfully, False otherwise
2336+ """
2337+ try :
2338+ # Use default sender if not provided
2339+ if not sender_email :
2340+ sender_email = settings .CLOUDCV_TEAM_EMAIL
2341+
2342+ # Render the HTML template
2343+ html_content = render_to_string (template_name , template_context )
2344+
2345+ # Create plain text version by stripping HTML tags
2346+ text_content = strip_tags (html_content )
2347+
2348+ # Create email message
2349+ email = EmailMultiAlternatives (
2350+ subject = subject ,
2351+ body = text_content ,
2352+ from_email = sender_email ,
2353+ to = [recipient_email ],
2354+ reply_to = [reply_to ] if reply_to else None ,
2355+ )
2356+
2357+ # Attach HTML version
2358+ email .attach_alternative (html_content , "text/html" )
2359+
2360+ # Send the email
2361+ email .send ()
2362+
2363+ logger .info (f"Email sent successfully to { recipient_email } " )
2364+ return True
2365+
2366+ except Exception as e :
2367+ logger .error (f"Failed to send email to { recipient_email } : { str (e )} " )
2368+ return False
2369+
2370+
2371+ def send_retention_warning_email (
2372+ challenge , recipient_email , submission_count , warning_date
2373+ ):
2374+ """
2375+ Send retention warning email using Django template.
2376+
2377+ Args:
2378+ challenge: Challenge object
2379+ recipient_email (str): Email address of the recipient
2380+ submission_count (int): Number of submissions affected
2381+ warning_date (datetime): Date when cleanup will occur
2382+
2383+ Returns:
2384+ bool: True if email was sent successfully, False otherwise
2385+ """
2386+ # Prepare template context
2387+ template_context = {
2388+ "CHALLENGE_NAME" : challenge .title ,
2389+ "CHALLENGE_URL" : f"{ settings .EVALAI_API_SERVER } /web/challenges/challenge-page/{ challenge .id } " ,
2390+ "SUBMISSION_COUNT" : submission_count ,
2391+ "RETENTION_DATE" : warning_date .strftime ("%B %d, %Y" ),
2392+ "DAYS_REMAINING" : 14 ,
2393+ }
2394+
2395+ # Add challenge image if available
2396+ if challenge .image :
2397+ template_context ["CHALLENGE_IMAGE_URL" ] = challenge .image .url
2398+
2399+ # Email subject
2400+ subject = f"⚠️ Retention Warning: { challenge .title } - { submission_count } submissions will be deleted in 14 days"
2401+
2402+ # Send the email
2403+ return send_template_email (
2404+ recipient_email = recipient_email ,
2405+ subject = subject ,
2406+ template_name = "challenges/retention_warning.html" ,
2407+ template_context = template_context ,
2408+ sender_email = settings .CLOUDCV_TEAM_EMAIL ,
2409+ )
2410+
2411+
23122412@app .task
23132413def send_retention_warning_notifications ():
23142414 """
@@ -2372,30 +2472,6 @@ def send_retention_warning_notifications():
23722472 )
23732473 continue
23742474
2375- challenge_url = f"{ settings .EVALAI_API_SERVER } /web/challenges/challenge-page/{ challenge .id } "
2376-
2377- template_data = {
2378- "CHALLENGE_NAME" : challenge .title ,
2379- "CHALLENGE_URL" : challenge_url ,
2380- "SUBMISSION_COUNT" : submission_count ,
2381- "RETENTION_DATE" : warning_date .strftime ("%B %d, %Y" ),
2382- "DAYS_REMAINING" : 14 ,
2383- }
2384-
2385- if challenge .image :
2386- template_data ["CHALLENGE_IMAGE_URL" ] = challenge .image .url
2387-
2388- # Get template ID from settings
2389- template_id = settings .SENDGRID_SETTINGS .get ("TEMPLATES" , {}).get (
2390- "RETENTION_WARNING_EMAIL" , None
2391- )
2392-
2393- if not template_id :
2394- logger .error (
2395- "RETENTION_WARNING_EMAIL template ID not configured in settings"
2396- )
2397- continue
2398-
23992475 # Get challenge host emails
24002476 try :
24012477 emails = challenge .creator .get_all_challenge_host_email ()
@@ -2414,16 +2490,28 @@ def send_retention_warning_notifications():
24142490 email_sent = False
24152491 for email in emails :
24162492 try :
2417- send_email (
2418- sender = settings .CLOUDCV_TEAM_EMAIL ,
2419- recipient = email ,
2420- template_id = template_id ,
2421- template_data = template_data ,
2422- )
2423- email_sent = True
2424- logger .info (
2425- f"Sent retention warning email to { email } for challenge { challenge .pk } "
2493+ success = send_retention_warning_email (
2494+ challenge = challenge ,
2495+ recipient_email = email ,
2496+ submission_count = submission_count ,
2497+ warning_date = warning_date ,
24262498 )
2499+ if success :
2500+ email_sent = True
2501+ logger .info (
2502+ f"Sent retention warning email to { email } for challenge { challenge .pk } "
2503+ )
2504+ else :
2505+ logger .error (
2506+ f"Failed to send retention warning email to { email } for challenge { challenge .pk } "
2507+ )
2508+ notification_errors .append (
2509+ {
2510+ "challenge_id" : challenge .pk ,
2511+ "email" : email ,
2512+ "error" : "Email sending failed" ,
2513+ }
2514+ )
24272515 except Exception as e :
24282516 logger .error (
24292517 f"Failed to send retention warning email to { email } for challenge { challenge .pk } : { e } "
0 commit comments