|
|
import gradio as gr
|
|
|
import pandas as pd
|
|
|
import io
|
|
|
import smtplib
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
from email.mime.text import MIMEText
|
|
|
from email.mime.base import MIMEBase
|
|
|
from email import encoders
|
|
|
import os
|
|
|
from datetime import datetime
|
|
|
from docx import Document
|
|
|
import tempfile
|
|
|
import subprocess
|
|
|
import platform
|
|
|
import zipfile
|
|
|
import shutil
|
|
|
import threading
|
|
|
import queue
|
|
|
import time
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
|
|
|
|
|
email_config = {
|
|
|
'smtp_server': '',
|
|
|
'smtp_port': 587,
|
|
|
'sender_email': '',
|
|
|
'sender_password': '',
|
|
|
'email_subject': 'Your Certificate of Participation',
|
|
|
'email_body': '''Dear {name},
|
|
|
|
|
|
Congratulations on successfully completing the webinar on "Modern Project Management Trends and Global Opportunities for Project Management"!
|
|
|
|
|
|
Please find your certificate of participation attached to this email.
|
|
|
|
|
|
This 2 PDU Programme is accepted by PMI-USA (PDU Code: 25675RL34G).
|
|
|
|
|
|
Best regards,
|
|
|
Pro Consultancy International (Pvt.) Ltd
|
|
|
PMI Authorized Training Partner'''
|
|
|
}
|
|
|
|
|
|
|
|
|
text_replacement_config = {
|
|
|
'placeholder_text': 'Name'
|
|
|
}
|
|
|
|
|
|
|
|
|
EMAIL_DELAY = 2
|
|
|
MAX_WORKERS = 3
|
|
|
|
|
|
def setup_email_server(smtp_server, smtp_port, sender_email, sender_password, email_subject, email_body):
|
|
|
"""Configure email server settings"""
|
|
|
global email_config
|
|
|
email_config = {
|
|
|
'smtp_server': smtp_server,
|
|
|
'smtp_port': int(smtp_port),
|
|
|
'sender_email': sender_email,
|
|
|
'sender_password': sender_password,
|
|
|
'email_subject': email_subject,
|
|
|
'email_body': email_body
|
|
|
}
|
|
|
return "β
Email server configured successfully!"
|
|
|
|
|
|
def setup_text_replacement(placeholder_text):
|
|
|
"""Configure text replacement for Word documents"""
|
|
|
global text_replacement_config
|
|
|
text_replacement_config['placeholder_text'] = placeholder_text
|
|
|
return f"β
Placeholder text set to: '{placeholder_text}'"
|
|
|
|
|
|
def convert_docx_to_pdf(docx_path):
|
|
|
"""Convert Word document to PDF using LibreOffice or system tools"""
|
|
|
try:
|
|
|
output_pdf = docx_path.replace('.docx', '.pdf')
|
|
|
print(f"π Converting {os.path.basename(docx_path)} to PDF...")
|
|
|
|
|
|
|
|
|
if platform.system() != 'Windows':
|
|
|
try:
|
|
|
print("π§ Using LibreOffice (Linux)...")
|
|
|
result = subprocess.run([
|
|
|
'libreoffice', '--headless', '--convert-to', 'pdf',
|
|
|
'--outdir', os.path.dirname(docx_path), docx_path
|
|
|
], check=True, capture_output=True, timeout=30)
|
|
|
|
|
|
if os.path.exists(output_pdf):
|
|
|
print(f"β
PDF created: {os.path.basename(output_pdf)}")
|
|
|
return output_pdf
|
|
|
else:
|
|
|
print(f"β οΈ LibreOffice: PDF not created, using DOCX")
|
|
|
return docx_path
|
|
|
except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e:
|
|
|
print(f"β οΈ LibreOffice not available or failed: {e}. Using DOCX file.")
|
|
|
return docx_path
|
|
|
else:
|
|
|
|
|
|
try:
|
|
|
print("πͺ Using win32com (Windows)...")
|
|
|
import win32com.client
|
|
|
import pythoncom
|
|
|
import time
|
|
|
|
|
|
pythoncom.CoInitialize()
|
|
|
word = None
|
|
|
try:
|
|
|
word = win32com.client.DispatchEx('Word.Application')
|
|
|
word.Visible = False
|
|
|
word.DisplayAlerts = 0
|
|
|
|
|
|
|
|
|
abs_docx = os.path.abspath(docx_path)
|
|
|
abs_pdf = os.path.abspath(output_pdf)
|
|
|
print(f"Opening: {abs_docx}")
|
|
|
doc = word.Documents.Open(abs_docx)
|
|
|
|
|
|
|
|
|
print(f"Saving as: {abs_pdf}")
|
|
|
doc.SaveAs2(abs_pdf, FileFormat=17)
|
|
|
|
|
|
|
|
|
doc.Close(SaveChanges=False)
|
|
|
|
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
|
|
|
|
if os.path.exists(output_pdf):
|
|
|
print(f"β
PDF created: {os.path.basename(output_pdf)}")
|
|
|
return output_pdf
|
|
|
else:
|
|
|
print(f"β PDF not created: {output_pdf}")
|
|
|
return docx_path
|
|
|
|
|
|
finally:
|
|
|
if word:
|
|
|
word.Quit()
|
|
|
pythoncom.CoUninitialize()
|
|
|
|
|
|
except ImportError as ie:
|
|
|
print(f"β οΈ win32com not installed: {ie}. Returning DOCX file.")
|
|
|
return docx_path
|
|
|
except Exception as e:
|
|
|
print(f"β Win32com PDF conversion error: {e}")
|
|
|
|
|
|
return docx_path
|
|
|
except Exception as e:
|
|
|
print(f"β PDF conversion error: {e}")
|
|
|
return docx_path
|
|
|
|
|
|
def replace_text_in_docx(docx_path, placeholder, replacement):
|
|
|
"""Replace placeholder text in Word document with student name"""
|
|
|
doc = Document(docx_path)
|
|
|
|
|
|
|
|
|
for paragraph in doc.paragraphs:
|
|
|
if placeholder in paragraph.text:
|
|
|
|
|
|
full_text = paragraph.text
|
|
|
if placeholder in full_text:
|
|
|
|
|
|
new_text = full_text.replace(placeholder, replacement)
|
|
|
|
|
|
|
|
|
if paragraph.runs:
|
|
|
first_run = paragraph.runs[0]
|
|
|
|
|
|
font_name = first_run.font.name
|
|
|
font_size = first_run.font.size
|
|
|
font_bold = first_run.font.bold
|
|
|
font_italic = first_run.font.italic
|
|
|
font_color = first_run.font.color.rgb if first_run.font.color.rgb else None
|
|
|
|
|
|
|
|
|
for run in paragraph.runs:
|
|
|
run.text = ""
|
|
|
|
|
|
|
|
|
paragraph.runs[0].text = new_text
|
|
|
paragraph.runs[0].font.name = font_name
|
|
|
paragraph.runs[0].font.size = font_size
|
|
|
paragraph.runs[0].font.bold = font_bold
|
|
|
paragraph.runs[0].font.italic = font_italic
|
|
|
if font_color:
|
|
|
paragraph.runs[0].font.color.rgb = font_color
|
|
|
|
|
|
|
|
|
for table in doc.tables:
|
|
|
for row in table.rows:
|
|
|
for cell in row.cells:
|
|
|
for paragraph in cell.paragraphs:
|
|
|
if placeholder in paragraph.text:
|
|
|
|
|
|
full_text = paragraph.text
|
|
|
if placeholder in full_text:
|
|
|
|
|
|
new_text = full_text.replace(placeholder, replacement)
|
|
|
|
|
|
|
|
|
if paragraph.runs:
|
|
|
first_run = paragraph.runs[0]
|
|
|
|
|
|
font_name = first_run.font.name
|
|
|
font_size = first_run.font.size
|
|
|
font_bold = first_run.font.bold
|
|
|
font_italic = first_run.font.italic
|
|
|
font_color = first_run.font.color.rgb if first_run.font.color.rgb else None
|
|
|
|
|
|
|
|
|
for run in paragraph.runs:
|
|
|
run.text = ""
|
|
|
|
|
|
|
|
|
paragraph.runs[0].text = new_text
|
|
|
paragraph.runs[0].font.name = font_name
|
|
|
paragraph.runs[0].font.size = font_size
|
|
|
paragraph.runs[0].font.bold = font_bold
|
|
|
paragraph.runs[0].font.italic = font_italic
|
|
|
if font_color:
|
|
|
paragraph.runs[0].font.color.rgb = font_color
|
|
|
|
|
|
return doc
|
|
|
|
|
|
def load_template(template_file):
|
|
|
"""Load Word document template"""
|
|
|
if template_file is None:
|
|
|
return None
|
|
|
|
|
|
file_ext = os.path.splitext(template_file)[1].lower()
|
|
|
|
|
|
if file_ext in ['.docx', '.doc']:
|
|
|
return template_file
|
|
|
else:
|
|
|
return None
|
|
|
|
|
|
def generate_certificate(template_path, name):
|
|
|
"""Generate certificate by replacing text in Word document"""
|
|
|
temp_dir = tempfile.mkdtemp()
|
|
|
output_docx = os.path.join(temp_dir, f"Certificate_{name.replace(' ', '_')}.docx")
|
|
|
|
|
|
|
|
|
doc = replace_text_in_docx(template_path, text_replacement_config['placeholder_text'], name)
|
|
|
doc.save(output_docx)
|
|
|
|
|
|
|
|
|
output_pdf = convert_docx_to_pdf(output_docx)
|
|
|
|
|
|
return output_pdf
|
|
|
|
|
|
def preview_with_test_name(template_file, test_name, placeholder_text="[NAME]"):
|
|
|
"""Preview certificate with test name"""
|
|
|
try:
|
|
|
|
|
|
setup_text_replacement(placeholder_text)
|
|
|
|
|
|
|
|
|
template_path = load_template(template_file)
|
|
|
if template_path is None:
|
|
|
return f"β Please upload a valid Word document (.docx)"
|
|
|
|
|
|
|
|
|
preview_cert = generate_certificate(template_path, test_name)
|
|
|
|
|
|
return f"β
Certificate will be generated with '{test_name}' replacing '{placeholder_text}'\n\nFile will be saved as PDF when sent to students."
|
|
|
except Exception as e:
|
|
|
return f"β Error: {str(e)}"
|
|
|
|
|
|
def send_email_with_certificate(recipient_email, recipient_name, cert_file_path):
|
|
|
"""Send certificate via email as PDF or DOCX"""
|
|
|
try:
|
|
|
print(f"π§ Preparing email for {recipient_name} ({recipient_email})...")
|
|
|
|
|
|
|
|
|
msg = MIMEMultipart()
|
|
|
msg['From'] = email_config['sender_email']
|
|
|
msg['To'] = recipient_email
|
|
|
msg['Subject'] = email_config.get('email_subject', 'Your Certificate of Participation')
|
|
|
|
|
|
|
|
|
body = email_config.get('email_body', 'Dear {name},\n\nPlease find your certificate attached.').format(name=recipient_name)
|
|
|
|
|
|
msg.attach(MIMEText(body, 'plain'))
|
|
|
|
|
|
|
|
|
if not os.path.exists(cert_file_path):
|
|
|
error_msg = f"Certificate file not found: {cert_file_path}"
|
|
|
print(f"β {error_msg}")
|
|
|
return False, error_msg
|
|
|
|
|
|
print(f"π Attaching: {os.path.basename(cert_file_path)}")
|
|
|
|
|
|
|
|
|
file_ext = os.path.splitext(cert_file_path)[1].lower()
|
|
|
|
|
|
if file_ext == '.pdf':
|
|
|
mime_type = 'application/pdf'
|
|
|
filename = f'Certificate_{recipient_name.replace(" ", "_")}.pdf'
|
|
|
elif file_ext == '.docx':
|
|
|
mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
|
filename = f'Certificate_{recipient_name.replace(" ", "_")}.docx'
|
|
|
else:
|
|
|
mime_type = 'application/octet-stream'
|
|
|
filename = f'Certificate_{recipient_name.replace(" ", "_")}{file_ext}'
|
|
|
|
|
|
|
|
|
with open(cert_file_path, 'rb') as f:
|
|
|
part = MIMEBase('application', 'octet-stream')
|
|
|
part.set_payload(f.read())
|
|
|
encoders.encode_base64(part)
|
|
|
part.add_header('Content-Disposition', f'attachment; filename="{filename}"')
|
|
|
part.add_header('Content-Type', mime_type)
|
|
|
msg.attach(part)
|
|
|
|
|
|
print(f"π Sending email to {recipient_email}...")
|
|
|
|
|
|
|
|
|
with smtplib.SMTP(email_config['smtp_server'], email_config['smtp_port']) as server:
|
|
|
server.starttls()
|
|
|
server.login(email_config['sender_email'], email_config['sender_password'])
|
|
|
server.send_message(msg)
|
|
|
|
|
|
print(f"β
Email sent successfully to {recipient_name}")
|
|
|
return True, None
|
|
|
except Exception as e:
|
|
|
error_msg = f"Email error: {str(e)}"
|
|
|
print(f"β {error_msg}")
|
|
|
return False, error_msg
|
|
|
|
|
|
def send_email_with_retry(recipient_email, recipient_name, cert_file_path, max_retries=3):
|
|
|
"""Send email with retry logic and delay"""
|
|
|
for attempt in range(max_retries):
|
|
|
try:
|
|
|
success, error = send_email_with_certificate(recipient_email, recipient_name, cert_file_path)
|
|
|
if success:
|
|
|
|
|
|
time.sleep(EMAIL_DELAY)
|
|
|
return True, None
|
|
|
else:
|
|
|
if attempt < max_retries - 1:
|
|
|
time.sleep(5)
|
|
|
continue
|
|
|
return False, error
|
|
|
except Exception as e:
|
|
|
if attempt < max_retries - 1:
|
|
|
time.sleep(5)
|
|
|
continue
|
|
|
return False, str(e)
|
|
|
return False, "Max retries exceeded"
|
|
|
|
|
|
def generate_single_certificate(template_path, name, placeholder_text):
|
|
|
"""Generate a single certificate - thread-safe"""
|
|
|
try:
|
|
|
|
|
|
temp_dir = tempfile.mkdtemp()
|
|
|
output_docx = os.path.join(temp_dir, f"Certificate_{name.replace(' ', '_')}.docx")
|
|
|
|
|
|
|
|
|
doc = replace_text_in_docx(template_path, placeholder_text, name)
|
|
|
doc.save(output_docx)
|
|
|
|
|
|
|
|
|
output_pdf = convert_docx_to_pdf(output_docx)
|
|
|
|
|
|
return output_pdf, None
|
|
|
except Exception as e:
|
|
|
return None, str(e)
|
|
|
|
|
|
def process_certificates(template_file, csv_file, send_emails, placeholder_text, progress=gr.Progress()):
|
|
|
"""Main function to process all certificates with threading"""
|
|
|
try:
|
|
|
print("\n" + "="*60)
|
|
|
print("π Starting certificate processing...")
|
|
|
print(f"π Send emails: {send_emails}")
|
|
|
print(f"π€ Placeholder text: '{placeholder_text}'")
|
|
|
print("="*60 + "\n")
|
|
|
|
|
|
|
|
|
if send_emails:
|
|
|
if not all([email_config.get('smtp_server'), email_config.get('sender_email'), email_config.get('sender_password')]):
|
|
|
error_msg = "β Please configure email server settings first!"
|
|
|
print(error_msg)
|
|
|
return error_msg, None
|
|
|
|
|
|
print(f"π§ Email configured:")
|
|
|
print(f" Server: {email_config['smtp_server']}:{email_config['smtp_port']}")
|
|
|
print(f" Sender: {email_config['sender_email']}")
|
|
|
print(f" Subject: {email_config.get('email_subject', 'Your Certificate')}")
|
|
|
|
|
|
|
|
|
setup_text_replacement(placeholder_text)
|
|
|
|
|
|
|
|
|
template_path = load_template(template_file)
|
|
|
if template_path is None:
|
|
|
error_msg = "β Could not load template file. Please upload a valid Word document (.docx)."
|
|
|
print(error_msg)
|
|
|
return error_msg, None
|
|
|
|
|
|
print(f"β
Template loaded: {os.path.basename(template_path)}\n")
|
|
|
|
|
|
|
|
|
df = pd.read_csv(csv_file)
|
|
|
print(f"π CSV loaded: {len(df)} students found")
|
|
|
print(f" Columns: {', '.join(df.columns.tolist())}\n")
|
|
|
|
|
|
|
|
|
if 'Name' not in df.columns:
|
|
|
error_msg = "β CSV must have a 'Name' column!"
|
|
|
print(error_msg)
|
|
|
return error_msg, None
|
|
|
|
|
|
if send_emails and 'Email address' not in df.columns:
|
|
|
error_msg = "β CSV must have an 'Email address' column for sending emails!"
|
|
|
print(error_msg)
|
|
|
return error_msg, None
|
|
|
|
|
|
results = []
|
|
|
total = len(df)
|
|
|
successful = 0
|
|
|
failed = 0
|
|
|
generated_files = []
|
|
|
|
|
|
print(f"π Phase 1: Generating {total} certificates in parallel (max {MAX_WORKERS} workers)...\n")
|
|
|
|
|
|
|
|
|
cert_futures = {}
|
|
|
|
|
|
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
|
|
|
|
|
for idx, row in df.iterrows():
|
|
|
name = row['Name']
|
|
|
email = row.get('Email address', '')
|
|
|
print(f" π Queued: {name}")
|
|
|
future = executor.submit(generate_single_certificate, template_path, name, placeholder_text)
|
|
|
cert_futures[future] = (name, email)
|
|
|
|
|
|
print(f"\nβοΈ Processing {len(cert_futures)} certificates...\n")
|
|
|
|
|
|
|
|
|
completed_count = 0
|
|
|
for future in as_completed(cert_futures):
|
|
|
name, email = cert_futures[future]
|
|
|
completed_count += 1
|
|
|
|
|
|
try:
|
|
|
cert_file, error = future.result()
|
|
|
|
|
|
if cert_file and os.path.exists(cert_file):
|
|
|
file_ext = os.path.splitext(cert_file)[1]
|
|
|
print(f" β
[{completed_count}/{total}] Generated {file_ext} for: {name}")
|
|
|
generated_files.append((cert_file, name, email))
|
|
|
if not send_emails:
|
|
|
successful += 1
|
|
|
results.append(f"β
{name}")
|
|
|
else:
|
|
|
print(f" β [{completed_count}/{total}] Failed for: {name} - {error if error else 'Unknown error'}")
|
|
|
failed += 1
|
|
|
results.append(f"β {name}: {error if error else 'Generation failed'}")
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f" β [{completed_count}/{total}] Exception for: {name} - {str(e)}")
|
|
|
failed += 1
|
|
|
results.append(f"β {name}: {str(e)}")
|
|
|
|
|
|
|
|
|
progress(completed_count / total, desc=f"π Generated {completed_count}/{total} certificates")
|
|
|
|
|
|
print(f"\nβ
Phase 1 complete: {len(generated_files)} certificates generated\n")
|
|
|
|
|
|
|
|
|
if send_emails and generated_files:
|
|
|
email_successful = 0
|
|
|
email_failed = 0
|
|
|
email_total = len(generated_files)
|
|
|
|
|
|
print(f"π§ Phase 2: Sending {email_total} emails sequentially...\n")
|
|
|
|
|
|
for idx, (cert_file, name, email) in enumerate(generated_files):
|
|
|
try:
|
|
|
print(f" π§ [{idx+1}/{email_total}] Sending to: {name} ({email})...")
|
|
|
success, error = send_email_with_retry(email, name, cert_file)
|
|
|
|
|
|
if success:
|
|
|
email_successful += 1
|
|
|
|
|
|
for i in range(len(results)):
|
|
|
if name in results[i]:
|
|
|
results[i] = f"β
{name} ({email})"
|
|
|
break
|
|
|
else:
|
|
|
print(f" β οΈ Email failed: {error}")
|
|
|
email_failed += 1
|
|
|
|
|
|
for i in range(len(results)):
|
|
|
if name in results[i]:
|
|
|
results[i] = f"β {name} ({email}): {error}"
|
|
|
break
|
|
|
|
|
|
|
|
|
progress((idx + 1) / email_total, desc=f"π§ Sent {idx + 1}/{email_total} emails")
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f" β οΈ Email exception: {str(e)}")
|
|
|
email_failed += 1
|
|
|
for i in range(len(results)):
|
|
|
if name in results[i]:
|
|
|
results[i] = f"β {name} ({email}): {str(e)}"
|
|
|
break
|
|
|
|
|
|
successful = email_successful
|
|
|
failed = email_failed
|
|
|
|
|
|
print(f"\nβ
Phase 2 complete: {email_successful} emails sent, {email_failed} failed\n")
|
|
|
|
|
|
|
|
|
zip_path = None
|
|
|
all_cert_files = [cert_file for cert_file, _, _ in generated_files] if generated_files else []
|
|
|
|
|
|
if all_cert_files:
|
|
|
try:
|
|
|
print(f"π¦ Creating ZIP file with {len(all_cert_files)} certificates...")
|
|
|
|
|
|
zip_dir = tempfile.mkdtemp()
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
zip_filename = f"Certificates_{timestamp}.zip"
|
|
|
zip_path = os.path.join(zip_dir, zip_filename)
|
|
|
|
|
|
|
|
|
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
|
for cert_file in all_cert_files:
|
|
|
if os.path.exists(cert_file):
|
|
|
|
|
|
zipf.write(cert_file, os.path.basename(cert_file))
|
|
|
|
|
|
print(f"β
ZIP file created: {zip_filename} ({os.path.getsize(zip_path) / 1024:.1f} KB)\n")
|
|
|
except Exception as e:
|
|
|
print(f"β Error creating ZIP: {e}\n")
|
|
|
zip_path = None
|
|
|
|
|
|
|
|
|
print("="*60)
|
|
|
print(f"π Processing complete!")
|
|
|
print(f" Total: {total} | Success: {successful} | Failed: {failed}")
|
|
|
print("="*60 + "\n")
|
|
|
|
|
|
summary = f"Processed: {total} | Success: {successful} | Failed: {failed}\n\n"
|
|
|
summary += "\n".join(results)
|
|
|
|
|
|
return summary, zip_path
|
|
|
|
|
|
except Exception as e:
|
|
|
error_msg = f"β Error: {str(e)}"
|
|
|
print(error_msg)
|
|
|
import traceback
|
|
|
print(traceback.format_exc())
|
|
|
return error_msg, None
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="Certificate Generator & Email Sender", theme=gr.themes.Soft()) as app:
|
|
|
gr.Markdown("""
|
|
|
# π Certificate Generator & Email Sender
|
|
|
Generate personalized certificates and optionally send them via email to participants.
|
|
|
|
|
|
Β© 2025 @sandaruabey2025
|
|
|
""")
|
|
|
|
|
|
with gr.Tab("1οΈβ£ Email Configuration"):
|
|
|
gr.Markdown("### Configure your email server settings (required only if sending emails)")
|
|
|
|
|
|
with gr.Row():
|
|
|
smtp_server = gr.Textbox(
|
|
|
label="SMTP Server",
|
|
|
placeholder="smtp.gmail.com",
|
|
|
value="smtp.gmail.com"
|
|
|
)
|
|
|
smtp_port = gr.Number(
|
|
|
label="SMTP Port",
|
|
|
value=587
|
|
|
)
|
|
|
|
|
|
with gr.Row():
|
|
|
sender_email = gr.Textbox(
|
|
|
label="Sender Email",
|
|
|
placeholder="your-email@gmail.com"
|
|
|
)
|
|
|
sender_password = gr.Textbox(
|
|
|
label="App Password",
|
|
|
placeholder="Your app-specific password",
|
|
|
type="password"
|
|
|
)
|
|
|
|
|
|
email_subject = gr.Textbox(
|
|
|
label="Email Subject",
|
|
|
value="Your Certificate of Participation",
|
|
|
placeholder="Subject line for certificate emails"
|
|
|
)
|
|
|
|
|
|
email_body = gr.Textbox(
|
|
|
label="Email Body (use {name} for student name)",
|
|
|
value="""Dear {name},
|
|
|
|
|
|
Congratulations on successfully completing the webinar on "Modern Project Management Trends and Global Opportunities for Project Management"!
|
|
|
|
|
|
Please find your certificate of participation attached to this email.
|
|
|
|
|
|
This 2 PDU Programme is accepted by PMI-USA (PDU Code: 25675RL34G).
|
|
|
|
|
|
Best regards,
|
|
|
Pro Consultancy International (Pvt.) Ltd
|
|
|
PMI Authorized Training Partner""",
|
|
|
lines=12,
|
|
|
placeholder="Email body template"
|
|
|
)
|
|
|
|
|
|
config_btn = gr.Button("πΎ Save Configuration", variant="primary")
|
|
|
config_status = gr.Textbox(label="Status", interactive=False)
|
|
|
|
|
|
config_btn.click(
|
|
|
fn=setup_email_server,
|
|
|
inputs=[smtp_server, smtp_port, sender_email, sender_password, email_subject, email_body],
|
|
|
outputs=config_status
|
|
|
)
|
|
|
|
|
|
gr.Markdown("""
|
|
|
**Note for Gmail users:**
|
|
|
1. Enable 2-Step Verification in your Google Account
|
|
|
2. Generate an App Password: [Google App Passwords](https://myaccount.google.com/apppasswords)
|
|
|
3. Use the generated 16-character password above
|
|
|
""")
|
|
|
|
|
|
with gr.Tab("2οΈβ£ Configure Certificate Template"):
|
|
|
gr.Markdown("""
|
|
|
### Configure your Word document certificate template
|
|
|
Upload your Word template and specify the placeholder text to replace
|
|
|
""")
|
|
|
|
|
|
template_preview = gr.File(
|
|
|
label="Upload Certificate Template (Word Document .docx only)",
|
|
|
file_count="single",
|
|
|
type="filepath"
|
|
|
)
|
|
|
|
|
|
with gr.Row():
|
|
|
with gr.Column():
|
|
|
test_name = gr.Textbox(
|
|
|
label="Test Name",
|
|
|
value="John Doe",
|
|
|
placeholder="Enter a test name"
|
|
|
)
|
|
|
|
|
|
placeholder_text_input = gr.Textbox(
|
|
|
label="Placeholder Text in Word Document",
|
|
|
value="Name",
|
|
|
placeholder="e.g., Name, [NAME], Your Name",
|
|
|
info="Exact text in Word document to replace with student name"
|
|
|
)
|
|
|
|
|
|
preview_btn = gr.Button("π Test Configuration", variant="secondary")
|
|
|
|
|
|
with gr.Column():
|
|
|
position_status = gr.Textbox(label="Status", interactive=False, lines=4)
|
|
|
|
|
|
preview_btn.click(
|
|
|
fn=preview_with_test_name,
|
|
|
inputs=[template_preview, test_name, placeholder_text_input],
|
|
|
outputs=position_status
|
|
|
)
|
|
|
|
|
|
gr.Markdown("""
|
|
|
**How to create your Word certificate:**
|
|
|
1. Design your certificate in Microsoft Word
|
|
|
2. Where you want the student name, type a placeholder like `[NAME]` or `Your Name`
|
|
|
3. Save as .docx format
|
|
|
4. Upload here and specify the exact placeholder text
|
|
|
|
|
|
**Example:** If your Word doc says "This certifies that [NAME] has completed...", use `[NAME]` as placeholder
|
|
|
""")
|
|
|
|
|
|
with gr.Tab("3οΈβ£ Generate & Send Certificates"):
|
|
|
gr.Markdown("### Upload certificate template and student data")
|
|
|
|
|
|
with gr.Row():
|
|
|
template_input = gr.File(
|
|
|
label="Certificate Template (Word Document .docx)",
|
|
|
file_count="single",
|
|
|
type="filepath"
|
|
|
)
|
|
|
csv_input = gr.File(
|
|
|
label="Student Data (CSV)",
|
|
|
file_count="single",
|
|
|
type="filepath"
|
|
|
)
|
|
|
|
|
|
placeholder_text_process = gr.Textbox(
|
|
|
label="Placeholder Text",
|
|
|
value="Name",
|
|
|
placeholder="e.g., Name, [NAME], Your Name",
|
|
|
info="Exact text in Word document to replace with student name"
|
|
|
)
|
|
|
|
|
|
send_email_checkbox = gr.Checkbox(
|
|
|
label="Send certificates via email (PDF if available, otherwise DOCX)",
|
|
|
value=False
|
|
|
)
|
|
|
|
|
|
gr.Markdown("""
|
|
|
**β‘ Performance Features:**
|
|
|
- β
Multi-threaded certificate generation (3 parallel workers)
|
|
|
- β
Smart email queue with 2-second delay between sends
|
|
|
- β
Automatic retry on email failures (3 attempts)
|
|
|
- β
Handles large CSV files efficiently
|
|
|
""")
|
|
|
|
|
|
process_btn = gr.Button("π Generate Certificates", variant="primary", size="lg")
|
|
|
|
|
|
with gr.Row():
|
|
|
results_output = gr.Textbox(
|
|
|
label="Processing Results",
|
|
|
lines=20,
|
|
|
max_lines=25
|
|
|
)
|
|
|
download_output = gr.File(
|
|
|
label="π¦ Download All Certificates (ZIP)",
|
|
|
interactive=False
|
|
|
)
|
|
|
|
|
|
process_btn.click(
|
|
|
fn=process_certificates,
|
|
|
inputs=[template_input, csv_input, send_email_checkbox, placeholder_text_process],
|
|
|
outputs=[results_output, download_output]
|
|
|
)
|
|
|
|
|
|
gr.Markdown("""
|
|
|
**π‘ Tip:** After generating certificates, you can download all of them as a ZIP file using the download button above.
|
|
|
""")
|
|
|
|
|
|
with gr.Tab("βΉοΈ Instructions"):
|
|
|
gr.Markdown("""
|
|
|
## How to Use This Application
|
|
|
|
|
|
### Step 1: Configure Email Settings (Optional)
|
|
|
- Only required if you want to send certificates via email
|
|
|
- For Gmail: Use an App Password, not your regular password
|
|
|
|
|
|
### Step 2: Create Word Certificate Template
|
|
|
1. Design your certificate in Microsoft Word
|
|
|
2. Where you want student name, type placeholder: `[NAME]`
|
|
|
3. Save as .docx format
|
|
|
4. Test configuration in Tab 2
|
|
|
|
|
|
### Step 3: Prepare Your CSV File
|
|
|
|
|
|
Required columns:
|
|
|
- `Name`: Participant's full name
|
|
|
- `Email address`: Participant's email (required only if sending emails)
|
|
|
|
|
|
Example CSV:
|
|
|
```
|
|
|
No,Name,Email address
|
|
|
1,John Doe,john@example.com
|
|
|
2,Jane Smith,jane@example.com
|
|
|
```
|
|
|
|
|
|
### Step 4: Generate & Send Certificates
|
|
|
- Upload Word template and CSV
|
|
|
- Enter placeholder text (e.g., `[NAME]`)
|
|
|
- Check "Send via email" to send as PDF
|
|
|
- Click "Generate Certificates"
|
|
|
|
|
|
### Supported File Formats
|
|
|
- **Certificate Template**: Word (.docx) only
|
|
|
- **Student Data**: CSV
|
|
|
|
|
|
### Tips
|
|
|
- Certificates are sent as PDF files via email
|
|
|
- Test with 1-2 students first
|
|
|
- Placeholder can appear multiple times in document
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
app.launch() |