#!/usr/bin/env python import os import time import shutil # copyfile, make_archive import argparse # argument parsing import sys import utils.moodle as moodle import utils.matnum as matnum_utils def sanity_check(matnums_csv, matnums_folder): """Check two cases for sanity: - Are there PDF files with no corresponding CSV entries? - Are there CSV entries with no provided PDF file? Args: matnums_csv (list): Matnums of all CSV entries matnums_folder (list): Matnums of all provided PDF files """ # PDF files with no entry in CSV: notfoundcsv = list(set(matnums_folder).difference(matnums_csv)) # Entries in CSV without PDF file notfoundpdf = list(set(matnums_csv).difference(matnums_folder)) # Report back if len(notfoundcsv) > 0: print('''Warning: Following {} matnums have PDFs but no entry in CSV: {}'''.format(len(notfoundcsv), ", ".join(notfoundcsv))) if len(notfoundpdf) > 0: print('''Warning: Following {} matnums have CSV entries but no PDF: {}'''.format(len(notfoundpdf), ", ".join(notfoundpdf))) print("Done.\n") return notfoundcsv, notfoundpdf def main(args): """Main routine """ # Parse input arguments parser = argparse.ArgumentParser(description=''' prepares batch upload to Moodle via assignment module. PDFs in folder 'in' are moved to folder 'tmp' with a certain folder structure and finally zipped to 'out'. Attention: zip-archive 'out' will be overwritten in the following! ''') parser.add_argument("-i", "--infolder", default="./pdfs_encrypted", help="Input folder with PDFs." + "Default: ./pdfs_encrypted") parser.add_argument("-c", "--csv", default="./Bewertungen.csv", help="Moodle grading sheet, needed to construct " + "submission folder names. " + "Default: ./Bewertungen.csv") parser.add_argument("--csvdelim", default=",", help="CSV delimiter. Default: ','") parser.add_argument("--csvquote", default='"', help="Quote character." + """Default: '"'""") parser.add_argument("--csvencoding", default="utf-8", help="CSV encoding scheme. Typical encodings:" + "'utf-8', 'utf-8-sig', or 'cp1252' (Windows). " + "Default: 'utf-8'") parser.add_argument("-o", "--outzip", default="./moodle_feedbacks.zip", help="Zip archive. Default: ./moodle_feedbacks.zip") parser.add_argument("-d", "--dry", action='store_true', help="Flag for dry run, displays folder structure") parser.add_argument("-t", "--tmp", default="./tmp", help="tmp folder. Default: ./tmp") parser.add_argument("--nowarn", action='store_true', help="Disables warnings") args = parser.parse_args(args) infolder = args.infolder sheet_csv = args.csv outzip = args.outzip tmpfolder = os.path.join(args.tmp, "to_be_zipped_for_moodle") dry = args.dry nowarn = args.nowarn csvdelim = args.csvdelim csvquote = args.csvquote csvenc = args.csvencoding # Print number of lines starttime = time.time() numstudents = moodle.get_student_number(sheet_csv=sheet_csv, csv_enc=csvenc) print('''Preparing for moodle upload Processing {} lines '''.format(numstudents)) # Clean up and create temporary folder dryout = "" if dry: print("Dry run\n") else: # Remove zip file if os.path.exists(outzip): os.remove(outzip) # Create temporary folder within given temporary directory if not os.path.isdir(tmpfolder): os.mkdir(tmpfolder) # Parse input folder # Only PDF files are considered with first digits # containing matriculation number matnums_folder = [] allfiles = os.listdir(infolder) allfiles.sort() allpdfs = [] for f in allfiles: if f.lower().endswith('.pdf') and matnum_utils.starts_with_matnum(f): allpdfs.append(f) matnums_folder.append(matnum_utils.get_matnum(f)) # Parse grading infos from CSV file infos = moodle.extract_info(sheet_csv=sheet_csv, csv_delim=csvdelim, csv_quote=csvquote, csv_enc=csvenc) # Loop over grading infos numfoundpdfs = 0 matnums_csv = [] moodleids = [] for cnt, info in enumerate(infos): # Copy PDF files # Find all PDFs starting with matriculation number, e.g. # '123456_Lastname_sheet.pdf' and '123456_Lastname_exam.pdf' # If pdf files for current student exists, create a directory and # copy the pdf files to it. The resulting directories can be # uploaded to Moodle matnum = info['matnum'] matnums_csv.append(matnum) moodleid = info['moodleid'] moodleids.append(moodleid) pdfs_student = [_ for _ in allpdfs if matnum == matnum_utils.get_matnum(_)] if len(pdfs_student) > 0: # Found at least one pdf numfoundpdfs += len(pdfs_student) # Prepare submission folder folder = moodle.submission_folder_name(info) longfolder = os.path.join(tmpfolder, folder) # Create folder if not dry: os.mkdir(longfolder) # Copy all files to folder for pdffile in pdfs_student: longpdffile = os.path.join(infolder, pdffile) longpdffiledest = os.path.join(longfolder, pdffile) if not dry: shutil.copyfile(longpdffile, longpdffiledest) else: dryout += "\n{}".format(os.path.join(folder, pdffile)) elif not nowarn: # No PDF found print("Warning: PDF for {matnum} (id={id}, name={name}) not found." .format(matnum=matnum, id=moodleid, name=info['fullname'])) # Print for-loop progress if not (cnt % max(1, round(numstudents/10))): print(".", sep=' ', end='', flush=True) # Print results print("Found {numpdf} PDFs (CSV had {numcsv} entries)" .format(numpdf=numfoundpdfs, numcsv=numstudents)) print("done.") # Sanity check: # Check for PDFs not reflected in CSV (student not registered in Moodle) sanity_check(matnums_csv, matnums_folder) # Zip if not dry: # Zip print("Zipping") shutil.make_archive(os.path.splitext(outzip)[0], 'zip', tmpfolder) print('Zip archive is stored at {}'.format(outzip)) # Delete temporary folder shutil.rmtree(tmpfolder) # Print dry run results else: print("\nDry run results:\n{}".format(dryout)) # Print status endtime = time.time() print("""Done. Time taken: {:.2f}""".format(endtime-starttime)) # Main routine if __name__ == '__main__': main(sys.argv[1:])