Created
October 8, 2024 10:11
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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