Skip to content

Instantly share code, notes, and snippets.

@NathanAdhitya
Created October 8, 2024 10:11
Show Gist options
  • Save NathanAdhitya/ea55d35f0f668331779d799337c0bd53 to your computer and use it in GitHub Desktop.
Save NathanAdhitya/ea55d35f0f668331779d799337c0bd53 to your computer and use it in GitHub Desktop.
Downloads student submissions in Google Classroom programatically and group them into folders based on name of students.
"""
This script helps you (as a teacher in Google Classroom) download all the files submitted by students.
I'm not sure where I partially copy pasted this from, but some parts have been modified by me to suit my needs.
Have fun!
- Nathan Adhitya
---
How to operate:
Preparing the script (conda example):
```bash
conda create -n gcr
conda activate gcr
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib --user
```
Operating the script:
1. Create a Google Cloud Platform project (if you haven't)
2. Enable the Google Classroom API
3. Enable the Google Drive API
4. In the OAuth2 consent screen, add the scopes present in the SCOPES list
5. Create a desktop client for the OAuth 2.0 Client ID
6. Download the credentials, rename it as credentials.json and place it in the same directory as gcr.py
7. Run the script `python ./gcr.py`
"""
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
import google.auth
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
import os
import time
# If modifying these scopes, delete the file token.json.
SCOPES = [
"https://www.googleapis.com/auth/classroom.courses.readonly",
"https://www.googleapis.com/auth/classroom.coursework.students",
"https://www.googleapis.com/auth/classroom.student-submissions.students.readonly",
"https://www.googleapis.com/auth/classroom.profile.emails",
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/classroom.rosters.readonly",
]
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists("token.json"):
creds = Credentials.from_authorized_user_file("token.json", SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open("token.json", "w") as token:
token.write(creds.to_json())
service = build("classroom", "v1", credentials=creds)
drive_service = build("drive", "v3", credentials=creds)
# List classes and prompt to pick one
def list_classes():
results = service.courses().list(pageSize=10).execute()
courses = results.get("courses", [])
if not courses:
print("No courses found.")
return None
for i, course in enumerate(courses):
print(f"{i + 1}. {course['name']}")
print("\r")
time.sleep(0.5)
choice = int(input("Pick a class by number: ")) - 1
return courses[choice]["id"], courses[choice]["name"]
# List assignments and prompt to pick one
def list_assignments(course_id):
results = service.courses().courseWork().list(courseId=course_id).execute()
assignments = results.get("courseWork", [])
if not assignments:
print("No assignments found.")
return None
# assignments.sort(key=lambda x: x["dueDate"], reverse=True)
for i, assignment in enumerate(assignments):
print(f"{i + 1}. {assignment['title']} (Due: {assignment['dueDate']})")
print("\r")
time.sleep(0.5)
choice = int(input("Pick an assignment by number: ")) - 1
return assignments[choice]["id"], assignments[choice]["title"]
# Download files submitted by students
def download_files(course_id, assignment_id, assignment_name):
results = (
service.courses()
.courseWork()
.studentSubmissions()
.list(courseId=course_id, courseWorkId=assignment_id)
.execute()
)
submissions = results.get("studentSubmissions", [])
if not submissions:
print("No submissions found.")
return
print(f"Found {len(submissions)} submissions.")
print("\r")
time.sleep(0.1)
for submission in submissions:
student_id = submission["userId"]
student_name = (
service.userProfiles().get(userId=student_id).execute()["name"]["fullName"]
)
print(f"Processing submission from {student_name}...")
if "assignmentSubmission" in submission:
assignment_submission = submission["assignmentSubmission"]
if "attachments" in assignment_submission:
print(f"Found {len(assignment_submission['attachments'])} attachments.")
for attachment in assignment_submission["attachments"]:
if "driveFile" in attachment:
file_id = attachment["driveFile"]["id"]
file_name = attachment["driveFile"]["title"]
file_path = (
f"files/{assignment_name}/{student_name}/{file_name}"
)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
request = drive_service.files().get_media(fileId=file_id)
with open(file_path, "wb") as f:
downloader = MediaIoBaseDownload(f, request)
done = False
while done is False:
status, done = downloader.next_chunk()
print(f"Download {int(status.progress() * 100)}%.")
course_id, course_name = list_classes()
if course_id:
assignment_id, assignment_name = list_assignments(course_id)
if assignment_id:
download_files(course_id, assignment_id, assignment_name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment