My brain is currently suffering from an overload caused by grading student assignments.
In search of a somewhat productive way to procrastinate, I thought I would share a small script I wrote sometime in 2023 to facilitate my grading work.
I use Moodle for all the classes I teach and students use it to hand me out their papers. When I'm ready to grade them, I download the ZIP archive Moodle provides containing all their PDF files and comment them using xournalpp and my Wacom tablet.
Once this is done, I have a directory structure that looks like this:
Assignment FooBar/ ├── Student A_21100_assignsubmission_file │ ├── graded paper.pdf │ ├── Student A's perfectly named assignment.pdf │ └── Student A's perfectly named assignment.xopp ├── Student B_21094_assignsubmission_file │ ├── graded paper.pdf │ ├── Student B's perfectly named assignment.pdf │ └── Student B's perfectly named assignment.xopp ├── Student C_21093_assignsubmission_file │ ├── graded paper.pdf │ ├── Student C's perfectly named assignment.pdf │ └── Student C's perfectly named assignment.xopp ⋮
Before I can upload files back to Moodle, this directory needs to be copied (I
have to keep the original files), cleaned of everything but the graded
paper.pdf
files and compressed in a ZIP.
You can see how this can quickly get tedious to do by hand. Not being a
complete tool, I often resorted to crafting a few spurious shell one-liners
each time I had to do this1. Eventually I got tired of ctrl-R
-ing my
shell history and wrote something reusable.
Behold this script! When I began writing this post, I was certain I had cheaped out on my 2021 New Year's resolution and written it in Shell, but glory!, it seems I used a proper scripting language instead.
#!/usr/bin/python3
# Copyright (C) 2023, Louis-Philippe Véronneau <pollo@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This script aims to take a directory containing PDF files exported via the
Moodle mass download function, remove everything but the final files to submit
back to the students and zip it back.
usage: ./moodle-zip.py <target_dir>
"""
import os
import shutil
import sys
import tempfile
from fnmatch import fnmatch
def sanity(directory):
"""Run sanity checks before doing anything else"""
base_directory = os.path.basename(os.path.normpath(directory))
if not os.path.isdir(directory):
sys.exit(f"Target directory {directory} is not a valid directory")
if os.path.exists(f"/tmp/{base_directory}.zip"):
sys.exit(f"Final ZIP file path '/tmp/{base_directory}.zip' already exists")
for root, dirnames, _ in os.walk(directory):
for dirname in dirnames:
corrige_present = False
for file in os.listdir(os.path.join(root, dirname)):
if fnmatch(file, 'graded paper.pdf'):
corrige_present = True
if corrige_present is False:
sys.exit(f"Directory {dirname} does not contain a 'graded paper.pdf' file")
def clean(directory):
"""Remove superfluous files, to keep only the graded PDF"""
with tempfile.TemporaryDirectory() as tmp_dir:
shutil.copytree(directory, tmp_dir, dirs_exist_ok=True)
for root, _, filenames in os.walk(tmp_dir):
for file in filenames:
if not fnmatch(file, 'graded paper.pdf'):
os.remove(os.path.join(root, file))
compress(tmp_dir, directory)
def compress(directory, target_dir):
"""Compress directory into a ZIP file and save it to the target dir"""
target_dir = os.path.basename(os.path.normpath(target_dir))
shutil.make_archive(f"/tmp/{target_dir}", 'zip', directory)
print(f"Final ZIP file has been saved to '/tmp/{target_dir}.zip'")
def main():
"""Main function"""
target_dir = sys.argv[1]
sanity(target_dir)
clean(target_dir)
if __name__ == "__main__":
main()
If for some reason you happen to have a similar workflow as I and end up using this script, hit me up?
Now, back to grading...
-
If I recall correctly, the lazy way I used to do it involved copying the directory, renaming the extension of the
graded paper.pdf
files, deleting all.pdf
and.xopp
files usingfind
and changinggraded paper.foobar
back to a PDF. Some clever regex or learningawk
from the ground up could've probably done the job as well, but you know, that would have required using my brain and spending spoons... ↩