Commit a03e4bab authored by Olivier's avatar Olivier
Browse files

Module M4: send assignments

The code in this commit is contributed by Germán Santa Maria.

New module M4, able to send personalized emails with unique
assignments as attachments
parent 2515693d
Script: email_assignments.py
============================
## Description
This script sends personalized emails with attachments.
## Script input
1. `output_assignments.csv`: This file contains the data from the students including their custom ID. It is the output file from the script [assign_customizer.py](../M2_customize_assignments/assign_customizer.py) from [module 2](../M2_customize_assignments).
1. All the pdf files with the assignments for the students, for example: `A12345678.pdf`. The contents of this folder are the output from the script [create_pdfs.sh](../M3_generate_assignments/create_pdfs.sh) from [module 3](../M3_generate_assignments).
1. Email settings are hard-coded in this script, except for the password. Sender email password is requested in a message box when the script is runing.
## Script output
1. One personalized email per student, at a controlled rate in order not to exceed local network limitations;
1. `sent_emails.csv` : file with the log of the sent emails
This script takes a list of the students who received customized assignments and the pdf files sent by them. Then it assigns two peers (reviewers) to each student, except to students that did not submit any solution to their assignment.
The output of the script is a `csv` file containing the consolidated data.
The input files should be placed in the `script_input` folder which is in the same path as the script. The output files will
be saved in the `script_output` folder.
## Script input
1. `output_assignments.csv`: This file contains the data from the students including their custom ID. It is the output file from the script [assign_customizer.py](../M2_customize_assignments/assign_customizer.py) from [module 2](../M2_customize_assignments).
1. All the `pdf` files with the solutions submitted by the students, named as: `customID.pdf` (for example `12345678.pdf`).
## Script output
1. `peers.csv`: file with the assigned peers for each student.
1. renamed pdfs, ready to be sent for grading: `ID_A.pdf``ID_G.pdf`
# -*- coding: utf-8 -*-
"""
Author: Germán Santa Maria
Script: Email Assignments
"""
"""-------------------------Hard coded input--------------------------------"""
# Input files paths
input_csv_path = 'script_input/output_assignments.csv'
assignments_pdfs_path = 'script_input/'
# Output files paths
output_csv_path = 'script_output/sent_emails.csv'
# (Hard-coded email settings)
host="myhost.example.com"
port = 587
sender_email = "sender@example.com"
sender_username = "username"
cc = "cc_address@example.com"
password = input("Type your password and press enter:")
# Delay time between sending two emails, in seconds
# For example, set 240 seconds if you are limited to less than 30 emails/hour
# and 40 seconds if the limit is less than 30 emails/10 min
delay_time = 45
"""-------------------------------------------------------------------------"""
# Import email sender function
import csv # For reading cvs files
import pathlib # For working with local paths
import smtplib, ssl # For email
import time # For timer
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# Initialize arrays to store data
names = [] # Student first name
last_name = [] # Student last name
emails = [] # Student email address
custom_ID_A = [] # Custom ID of the assignment for the student
custom_ID_G = [] # Custom ID of the grading file for the student
attachments = [] # Name of the attachment for each student
email_bodies = [] # Body of the email for each student
text_emails = [] # Array with the complete email text for each student
email_log = [] # Array with the log of successfully sent emails
# Store students data from csv file into variables
with open(input_csv_path, newline='') as f:
student_info_reader = csv.reader(f)
next(student_info_reader) # Skip header [name, last name, email, ...]
student_data = [row for row in student_info_reader]
for row in student_data:
names.append(row[0])
last_name.append(row[1])
emails.append(row[2])
custom_ID_A.append(row[3])
custom_ID_G.append(row[4])
for ID in custom_ID_A:
attachment_name = "assignment_a01_ID_"+ID+".pdf"
attachments.append(attachment_name)
# Make sure that all the attachments are in the script_input folder by looking
# for a file with the name "assignment_a01_ID_XXXXXXXXX.pdf"
for i in range(0, len(custom_ID_A)):
file_to_search = pathlib.Path(assignments_pdfs_path+attachments[i])
if file_to_search.exists() != True:
raise Exception('The file ' +
assignments_pdfs_path+attachments[i]+
' was not found, no emails were sent')
"""-----------------Build email text for each student-----------------------"""
# Write a personalized email body for each student
standard_body = """Dear XXstudent_nameXX,
I have the pleasure of handing you (attached) your first coursework assignment in our course. I hope you will participate in the program, and have fun doing so! The coursework program has a total of three assignments, and is managed entirely by e-mail. Below, I explain how the program works and what to expect.
(insert email body here)
%%% Format of your answer
Please send one PDF file containing your complete answer. The PDF file should be named:
XXsubmissionfilenameXX
Send your file to this email address as an attachment (the title and content of the email do not matter; they will not be read).
Your answer will be graded anonymously, and so it must not contain *any* personally-identifying information: no name, no pseudonym, no matriculation number of any kind.
You can write your answer as you like (i.e. by hand, or by typing it up), and create a PDF with your preferred tools. It only matters that your answer arrives as a clearly-legible PDF document.
%%% Conclusion
This is a lot of explanations, but in principle, this should be fun, and the coursework is not very hard. If you have any question about how this program works, you can contact me at (redacted). I look forward to receiving your answer!
With best regards,
(author)
"""
for i in range(0, len(names)):
temp_body = standard_body.replace("XXstudent_nameXX", names[i])
submission_file_name = custom_ID_A[i] + ".pdf"
temp_body = temp_body.replace("XXsubmissionfilenameXX", submission_file_name)
email_bodies.append(temp_body)
# Compose email text for each student
for i in range(0, len(names)):
# Reciever email
receiver_email = emails[i]
# Email subject
subject = "Custom Assignment #1 "+custom_ID_A[i]
# Email body
body = email_bodies[i]
# Attachment
filename = attachments[i]
# Set headers
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = subject
message["Cc"] = cc
# Add body to email
message.attach(MIMEText(body, "plain"))
# Add attachment
attachment_path = assignments_pdfs_path+filename # Attachment folder path
# Open PDF file in binary mode
with open(attachment_path, "rb") as attachment:
# Add file as application/octet-stream
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
# Encode file in ASCII characters to send by email
encoders.encode_base64(part)
# Add header as key/value pair to attachment part
part.add_header("Content-Disposition",
f"attachment; filename= {filename}",)
# Add attachment to message and convert message to string
message.attach(part)
text = message.as_string()
text_emails.append(text)
"""-------------------------------------------------------------------------"""
# Send an email to each student with the corresponding attachment
"""------------------------------Send emails--------------------------------"""
# Create a secure SSL context
context = ssl.create_default_context()
# Check if the login data is correct
try :
server = smtplib.SMTP(host = host, port = port)
server.starttls(context = context) #Encryption
server.login(sender_username, password)
print('Login to: '+ sender_email + ' was successful ')
except Exception as e:
# Print error message
print('Login to: '+ sender_email +
' was not successful, no emails were sent ')
raise Exception(e)
server.close() # Logout
# Send email to each student
for i in range(0, len(names)):
try :
# Reciever email
receiver_email = emails[i]
# Email content
text = text_emails[i]
# Logging into email server
server = smtplib.SMTP(host = host, port = port)
server.starttls(context = context) #Encryption
server.login(sender_username, password)
# Send email
server.sendmail(sender_email, [receiver_email, cc], text)
# Logout
server.close() # Logout
# Print email log
local_time = time.time()
time_sent = time.ctime(local_time)
log_m = 'Email was sent to: ' + receiver_email + ' time: ' + time_sent
print(log_m)
except Exception as e:
# Print error
log_m = 'Error while trying to send email to: ' + receiver_email
print(log_m)
print(e)
log_m = 'Email could not be sent, error: ' + str(e)
# Save log
email_log.append(log_m)
# Wait before sending the next email
time.sleep(delay_time)
# Store email log in a csv file
with open (output_csv_path,'w',newline='') as f:
writer = csv.writer(f)
writer.writerow(['name','lastname','email','custom_ID_A','custom_ID_G',
'log'])
for i in range(0,len(names)):
writer.writerow([names[i],last_name[i],emails[i],custom_ID_A[i],
custom_ID_G[i], email_log[i]])
"""-------------------------------------------------------------------------"""
# End of the script
......@@ -24,7 +24,7 @@ The code in this repository is published under the [GNU GPL v3](href="https://ww
A total of nine modules (six of which being locally-run scripts) are needed to run the project.
[ ] Module 1: Collect participant information
(in Moodle or through email)
(through email)
[x] [Module 2](M2_customize_assignments): Generate custom variables for assignments
(in Python)
......@@ -32,11 +32,11 @@ A total of nine modules (six of which being locally-run scripts) are needed to r
[x] [Module 3](M3_generate_assignments): Generate assignments and solutions
(in bash + LaTeX)
[ ] Module 4: Send assignments to students
[x] [Module 4](M4_send_assignments): Send assignments to students
(in Python)
[ ] Module 5: Receive submissions from students
(in Moodle or through email)
(through email)
[x] [Module 6](M6_match_peers): Match peers
(in Python)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment