A desktop application for uploading student submissions to Google Drive and automatically linking them in your Google Sheets gradebook. This tool streamlines the process of managing assignment submissions by organizing files in Google Drive and updating spreadsheet entries with direct links.
- Intelligent File Matching: Match submission files by student ID or first/last name from Moodle exports
- Batch Upload: Upload multiple student submission files to Google Drive
- Automatic Linking: Automatically update Google Sheets with clickable hyperlinks to uploaded files
- Link Conflict Prevention: Skip files that would overwrite existing submission links
- Dual Interface: User-friendly PyQt5 GUI and powerful command-line interface
- Smart Configuration: Persistent configuration with auto-loading and manual save functionality
- Credentials Detection: Automatic detection of Google API credentials with helpful error messages
- Enhanced Authentication: Visual status indicators and improved OAuth flow
- Unicode Support: Handles international characters in filenames safely
- Progress Tracking: Real-time upload progress with detailed logging and emoji indicators
- Python 3.6 or higher
- Google account with access to Google Drive and Google Sheets
- Google Cloud Project with Drive and Sheets API enabled
- Install dependencies:
pip install -r requirements.txt - Set up Google API credentials (see Google API Setup)
- Run the application:
python app.py - Follow the improved workflow:
- ✅ Configuration auto-loads from
config.json(if exists) - 🔐 Click "Authorize App" (now at the top) - credentials detection included
- ⚙️ Fill/modify configuration fields as needed
- 📁 Use "Select Folder" button next to submissions path field
- 💾 Click "Save Configuration" to persist changes
- 🚀 Click "Start Upload" to begin processing
- ✅ Configuration auto-loads from
- Copy configuration:
copy config.sample.json config.json - Edit
config.jsonwith your settings - Set up authentication:
python cli_auth.py setup - Upload files:
python uploader.py /path/to/submissions
pip install -r requirements.txt- Go to the Google Cloud Console
- Create a new project or select an existing one
- Enable the following APIs:
- Google Drive API
- Google Sheets API
- In the Google Cloud Console, navigate to APIs & Services > Credentials
- Click + CREATE CREDENTIALS > OAuth client ID
- If prompted, configure the OAuth consent screen:
- Choose External user type (unless you're in a Google Workspace organization)
- Fill in the required information:
- App name: "Submission Uploader"
- User support email: Your email
- Developer contact information: Your email
- Add your email to test users during development
- For the OAuth client ID:
- Application type: Desktop application
- Name: "Submission Uploader Desktop Client"
- Download the credentials JSON file
- Rename the downloaded file to
credentials.jsonand place it in the project root directory
The application will generate a token.json file automatically when you first authorize it:
- Run the application
- Click the "Authorize App" button
- Your web browser will open to Google's authorization page
- Sign in to your Google account
- Grant the requested permissions:
- See, edit, create, and delete your Google Drive files
- See, edit, create, and delete your spreadsheets in Google Sheets
- The authorization will complete and
token.jsonwill be created automatically
Note: The token.json file contains your authorization token and should be kept secure. Don't share it or commit it to version control.
The application now uses a config.json file that loads into the GUI on startup with manual save functionality.
- GUI (app.py): Automatically loads settings from
config.jsonon startup - Manual Save: Click "Save Configuration" button to save changes to
config.json - CLI (uploader.py): Reads configuration from
config.jsonat runtime - Unified Settings: Saved changes from GUI are immediately available for CLI usage
-
Copy the sample configuration:
copy config.sample.json config.json
-
Edit your configuration: Open
config.jsonand update the following fields:google_sheets.sheet_id: Your Google Sheet IDgoogle_sheets.sheet_name: Sheet tab name (e.g., "A2", "Submissions")google_sheets.id_column: Column containing student IDs (e.g., "A")google_sheets.first_name_column: Column containing first names (e.g., "B")google_sheets.last_name_column: Column containing last names (e.g., "C")google_sheets.link_column: Column for file links (e.g., "N")google_sheets.start_row: Data start row (usually 2 or 3)google_drive.folder_id: Target Google Drive folder IDsubmissions.folder_path: Default path to submissions folder (optional)
{
"google_sheets": {
"sheet_id": "YOUR_SHEET_ID_HERE",
"sheet_name": "Sheet1",
"id_column": "A",
"first_name_column": "B",
"last_name_column": "C",
"link_column": "N",
"start_row": 3
},
"google_drive": {
"folder_id": "YOUR_FOLDER_ID_HERE"
},
"submissions": {
"folder_path": ""
},
"authentication": {
"scopes": [
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/spreadsheets"
],
"credentials_file": "credentials.json",
"token_file": "token.json"
},
"output": {
"summary_file": "upload_summary.txt"
},
"upload": {
"mime_type": "application/octet-stream",
"permissions": {
"role": "reader",
"type": "anyone"
}
}
}To support course assignments where submissions are uploaded by one group member and should be recorded under a group name, the uploader can load a local CSV that maps members to group names. Add a group_mappings block to config.json pointing to your CSV and the column names to use:
"group_mappings": {
"file": "C:\\path\\to\\groups.csv",
"member_first_name_column": "First Name",
"member_last_name_column": "Last Name",
"group_name_column": "Group Name"
}Notes:
- The CSV must be readable from the path in
file(absolute or relative to the project root). - The uploader looks for headers case-insensitively but you should provide the exact header names when possible. The expected headers are
First Name,Last Name, andGroup Name. - When grouped mode is enabled (the CSV loads successfully), the uploader will, for each filename, attempt the following in order:
- Check
matches.csv(pre-computed filename -> group matches) - Extract a student name from the filename and map the student to their group using the CSV
- Fallback name extraction strategies (simple name parts)
- Check
If a mapped group name is not present in the sheet ID column, the uploader will create a new row and write the group name into the ID column before writing the link.
python app.pyEnhanced Features:
- Auto-load Configuration: Settings automatically populate from
config.jsonon startup - Integrated Folder Selection: Select submissions folder with button positioned next to the path field
- Smart Authentication Flow:
- "Authorize App" button moved to top for better workflow
- Automatic credentials file detection
- Visual status indicators with colors and emojis
- Error messages when credentials are missing
- Manual Configuration Save: Click "Save Configuration" to persist changes
- Real-time Feedback: Visual confirmation of save operations and authentication status
Use the CLI authentication tool to generate your token:
# Check if authentication is already set up
python cli_auth.py check
# Set up new authentication (opens browser)
python cli_auth.py setup
# Get help
python cli_auth.py helppython uploader.py /path/to/submissions/folderIf you want to verify how files will be mapped before performing any uploads, use the dry-run flag. This maps filenames to groups/IDs using matches.csv and the optional group_mappings CSV, and writes a summary to upload_summary.txt without contacting Google APIs.
# Run a mapping-only dry-run and write results to upload_summary.txt
python uploader.py --dry-run
# Or provide a folder path and dry-run in one command
python uploader.py --dry-run "C:\\path\\to\\submissions"The dry-run summary includes:
- total files scanned
- files that would be mapped (filename -> group/ID and match method)
- files with no mapping (skipped)
Check upload_summary.txt in the project root after the dry-run completes.
Prerequisites for CLI:
config.jsonmust exist with your settingscredentials.jsonmust be downloaded from Google Cloud Console- Authentication token must be generated (use
cli_auth.py setup)
- Sheet ID: The ID from your Google Sheets URL (the long string between
/d/and/edit) - Sheet Name: The name of the specific sheet tab (e.g., "Sheet1", "Gradebook")
- ID Column: Column letter containing student IDs (e.g., "A", "B", "C")
- Link Column: Column letter where file links will be placed (e.g., "D", "E", "F")
- Start Row: Row number where student data begins (usually 2 if row 1 has headers)
- Google Drive Folder ID: The ID of the destination folder (from the folder's URL)
Supported File Formats: PDF, DOCX, and other common document formats
File Matching Strategies:
- Primary: Student ID matching from Moodle export format
- Secondary: First and last name matching when student ID fails
Moodle Export Naming Format:
FirstName LastName_SubmissionID_assignsubmission_file_StudentID_COMP1600_A1.pdf
Examples:
Aadam Seenath_1835025_assignsubmission_file_816050357_COMP1600_A1.pdfAaron Charran_1835097_assignsubmission_file_816049096_ COMP1600_A1.pdfBilly Dee Williams_1834952_assignsubmission_file_816044707_COMP1600_A1.pdf
Legacy Format Support: The system also supports older naming conventions like:
816040296_A2.pdf320053318_A2.pdf400014890.A2.pdf
Smart Matching Algorithm:
- Primary: Extract student ID from filename using regex patterns
- Secondary: When ID matching fails, extract and match names:
- Exact first name + last name match
- First name + partial last name match
- Normalized comparison (handles hyphens, spaces, case differences)
- Conflict Prevention: Skip files where target cells already contain links
Spreadsheet Requirements:
- ID Column: Contains student IDs (e.g., 816050357, 320053318)
- First Name Column: Contains first names for name-based matching
- Last Name Column: Contains last names for name-based matching
- Link Column: Target column for submission file links (skipped if already populated)
-
Install PyInstaller:
pip install pyinstaller
-
Ensure all dependencies are installed:
pip install -r requirements.txt
Windows:
pyinstaller --onefile --windowed --add-data "credentials.json;." --name "SubmissionUploader" app.pyMac/Linux:
pyinstaller --onefile --windowed --add-data "credentials.json:." --name "SubmissionUploader" app.pyWindows:
pyinstaller --onefile --add-data "credentials.json;." --name "SubmissionUploaderCLI" uploader.pyMac/Linux:
pyinstaller --onefile --add-data "credentials.json:." --name "SubmissionUploaderCLI" uploader.py--onefile: Creates a single executable file--windowed: Suppresses console window (GUI only)--add-data: Includes credentials.json in the build--name: Sets the executable name
Create a build.spec file for more control:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['app.py'],
pathex=[],
binaries=[],
datas=[('credentials.json', '.')],
hiddenimports=[
'google.oauth2.credentials',
'google_auth_oauthlib.flow',
'googleapiclient.discovery',
'googleapiclient.http'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='SubmissionUploader',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='icon.ico' # Optional: add your own icon
)Then build with:
pyinstaller build.specThe built executable will be in the dist/ folder. For distribution:
-
Include required files:
- The executable
config.json(template or pre-configured)- Installation instructions
-
Create distribution folder:
SubmissionUploader_v1.0/ ├── SubmissionUploader.exe (or SubmissionUploader on Mac/Linux) ├── config.json ├── README.txt └── credentials_setup_guide.txt
Common Issues:
- Missing modules: Add to
hiddenimportsin spec file - File not found errors: Ensure all data files are included with
--add-data - Large executable size: Consider using
--exclude-modulefor unused packages - Slow startup: Use
--onedirinstead of--onefilefor faster startup
Testing the Build:
- Test the executable on a clean system without Python
- Verify all features work correctly
- Check that credentials.json is properly bundled
- Test both GUI and authentication flows
- "Configuration file not found": Copy
config.sample.jsontoconfig.json - UI not loading previous settings: Check that
config.jsonis valid JSON - Settings not saving: Click "Save Configuration" button and ensure write permissions in the project directory
-
❌ "No credentials.json file found":
- Download credentials from Google Cloud Console
- Rename to
credentials.jsonand place in project root - "Authorize App" button will be disabled until credentials are found
-
⚠️ "Token Status: No Token Found":- GUI: Click "Authorize App" button (now at the top)
- CLI: Run
python cli_auth.py setup
-
⚠️ "Token Status: Expired, Reauthorization Needed":- App will attempt automatic refresh
- If refresh fails, click "Authorize App" again
-
❌ "Authorization Failed":
- Verify
credentials.jsonis valid JSON - Check Google Cloud Console project settings
- Ensure OAuth consent screen is configured
- Verify
-
GUI shows red error messages:
- Red text indicates missing or invalid credentials
- Green text indicates successful authentication
- Orange text indicates warnings or expired tokens
-
File matching problems:
- Check that filenames follow Moodle export format or contain student IDs
- Verify first/last name columns are correctly configured for name-based matching
- Review upload log for specific matching failures
-
"Link already exists, skipping...":
- This is normal behavior - prevents overwriting existing submissions
- Files are automatically skipped if the target spreadsheet cell already has content
-
Permission errors:
- Verify Google account has edit access to the target Drive folder and Sheet
- Check that OAuth scopes include both Drive and Sheets permissions
-
API quota exceeded:
- Google APIs have usage limits; wait and retry if needed
- Consider smaller batch sizes for large numbers of files
-
Configuration not loading:
- Ensure
config.jsonexists and is valid JSON - Check file permissions in the project directory
- Ensure
-
"Select Folder" button not working:
- Button is now positioned between the label and text field
- Selected folder automatically updates the submissions path configuration
-
Settings not persisting:
- Click "Save Configuration" button after making changes
- Manual save prevents accidental overwrites during form editing
- "Invalid JSON in configuration file": Validate your
config.jsonsyntax - Token generation fails: Ensure
credentials.jsonis in the correct location - Upload script can't find config: Run from the same directory as
config.json
To find your Google Drive folder ID:
- Open Google Drive in your browser
- Navigate to the desired folder
- Copy the folder ID from the URL:
https://drive.google.com/drive/folders/FOLDER_ID_HERE
Sub_Uploader/
├── app.py # Main GUI application with enhanced UI
├── uploader.py # CLI upload script with name matching
├── cli_auth.py # CLI authentication helper tool
├── config.json # Configuration file (auto-loads in GUI)
├── credentials.json # Google OAuth credentials (required)
├── token.json # Generated auth token (auto-created)
├── requirements.txt # Python dependencies
├── readme.md # Documentation (this file)
├── upload_summary.txt # Upload report (generated after each run)
├── app.spec # PyInstaller spec file (optional)
├── submissions/ # Example submissions folder with Moodle exports
│ ├── Aadam Seenath_1835025_assignsubmission_file_816050357_COMP1600_A1.pdf
│ ├── Aaron Charran_1835097_assignsubmission_file_816049096_ COMP1600_A1.pdf
│ └── ...
├── build/ # PyInstaller build artifacts
└── dist/ # Built executables
├── SubmissionUploader.exe (Windows)
└── SubmissionUploaderCLI.exe
- app.py: Enhanced GUI with improved authentication flow, smart folder selection, and visual feedback
- uploader.py: CLI version with intelligent file matching (student ID + name fallback)
- config.json: Unified configuration file that auto-loads in GUI and supports manual save
- credentials.json: OAuth credentials from Google Cloud Console (must be added by user)
- token.json: Auto-generated authentication token (keep secure)
- submissions/: Contains student submission files from Moodle exports
- Keep
credentials.jsonsecure and don't share it publicly - The
token.jsonfile contains access tokens - treat it as sensitive - Consider using environment variables for sensitive data in production
- Regularly review and rotate OAuth credentials if needed