{ "info": { "author": "Bernard Badzioch", "author_email": "bernard.badzioch@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "# ubgrade\n\nThis package contains scripts automating some tasks related to preparation\nand grading of exams.\n\nInstallation:\n\n```\npip install ubgrade\n```\n\n**Note:** `ubgrade` uses the `pyzbar` library to read QR codes on exam pages. While `pyzbar`\nis automatically installed by running the above command, on Max OS X and Linux it requires\na separate installation of the `zbar` shared library. See the\n[`pyzbar` installation instructions](https://pypi.org/project/pyzbar/) for details.\n\nImport:\n\n```\nimport ubgrade\n```\n\n\n## 1. Exam Preparation\n\n**1.1.** Use LaTeX exam template file [`exam_template.tex`](https://raw.githubusercontent.com/bbadzioch/ubgrade/master/ubgrade/exam_template.tex) to prepare the exam.\nThe cover page of the template should be left unchanged, aside from editing\nthe title, date, and exam instructions. The format of the remaining\npages can be changed as needed, but the top margin should be set to at least\n1.5 inches to leave enough space for a QR code.\n\n\n\n**1.2.** Once the exam is compiled to a pdf file, use the function\n`ubgrade.make_exams` to produce copies of the exam with QR codes embedded\nin each page. The signature of this functions is as follows:\n\n```\nubgrade.make_exams(template, N, qr_prefix, output_file=None, output_directory = None, add_backpages = False)\n```\n\n* `template`: Name of the pdf file with the exam.\n\n* `N`: The number of copies of the exam that are to be produced. \n\n* `qr_prefix`: Prefix of QR codes to be added to the exam pages. The QR code for each page will be of the form\n`(qr_prefix)_C(copy number)_P(page number)`. (e.g. `MTH309_C002_P03`, for the 3rd page of the second copy of\nthe exam with `qr_prefix =\"MTH309\"`).\n\n* `output_file`: Name of pdf files to be produced; these files will be named `output_file_n.pdf` where `n`\nis the number of the exam copy. If `output_file` is None, the name of the template file will be used.\n\n* `output_directory`: Name of the directory where the produced pdf files will\nbe saved. If None, the current directory will be used. If the given directory\ndoes not exist, it will be created.\n\n* `add_backpages`: Boolean. If `True` back pages will be added to the produced pdf files, with a message that these\npages will not be graded. This is intended for two-sided printing of the exam files.\n\n\n## 2. Preparation for grading\n\n**2.1.** After the exam has been administered scan exam copies to pdf files.\nFor best results use photo/text scanner setting. Black and white low resulution\nscans may create problems. The scanned exam pages can be oriented sideways or\nupside down. The orientation will be adjusted as needed, provided that all pages\nin any given scanned file have the same orientation (different orientation in\ndifferent files is fine).\n\n**2.2.** Create a directory (which we will subsequently call *the main grading directory*)\nin which all grading files will reside. Inside this directory create a subdirectory\nnamed `scans` and place the scanned pdf files there.\n\n**2.3.** Create a csv file with the roster of students taking the exam. This file\nshould have at least two columns. The column with the heading `person_number`\nshould be populated with person numbers of students taking the exam. The column\nwith the heading `email` should contain email addresses of students (which will\nbe needed to send graded exams back to students). Columns with other data\n(student names etc.) can be included as well. The header row should be the first\nrow of the csv file. Place the file in the main grading directory. We will refer\nto this file as *the gradebook file*.\n\n**2.4.** Use the function `ubgrade.prep_grading` to prepare grading files.\nThe signature of this function is as follows:\n\n```\nubgrade.prep_grading(maxpoints, main_dir = None, gradebook = None, rotate = None, skip_codes = [], batch = False, files = None, init_grading_data = False)\n```\n\n* `maxpoints`: A list with the maximal possible score of each exam page.\nThis argument can be also given as an integer, if all pages have the same\nmaximal score. If the maximal possible score corresponding to a page is 0,\nit indicates that the page will not be graded: there will be no score table\nadded to it etc.\n\n* `main_dir`: The main grading directory. If not specified the current directory will be used.\n\n* `gradebook`: The name of the gradebook file. This file needs to be located in\nthe main grading directory. If `None` it will be assumed that the file name\nis `gradebook.csv`\n\n* `rotate`: This argument can be an integer (a multiple of 90) giving\nthe angle by which all pages of pdf files should rotated clockwise to bring them\nto the correct orientation. If `None` (default), the angle of rotation of each file will be\nautomatically detected, using the assumption that on a correctly oriented page\nthe QR code is located in the upper right corner. The automatic angle detection\nwill check the angle of rotation for each pdf file separately, but all pages in\na given file will be rotated by the same angle. \n\n* `skip_codes`:\nA list of of strings. If the content of a QR code detected on an exam page matches one of the\nstrings on this list, the page will be skipped over and will not be processed at all. This can\nbe useful if e.g. scanned exam files include pages with scratchwork which should be ignored. \n\n* `batch`: Boolean. By default, if the function encounters pages where QR code\nor person number which be read, it will ask the user to enter this\ndata. If `batch = True`, the function will instead quietly process all pages.\nPages with missing data will be assembled into a separate pdf file, which can\nbe processed at a later time by running this function again with `batch = False`. \n\n* `files`: This argument specifies which files in the `scans` subdirectory should\nbe processed. If `None` (default) all files will be processed, except for the ones that\nwere already processed during previous runs of the function. This is what one should want\nin most cases. If `files = \"all\"` all files will be processed, without exceptions.\nThe value of this argument can be also a list of file names, explicitly specifying which\nfiles in the `scans` subdirectory should be processed.\n\n* `init_grading_data`: Boolean. If `True` it will reset metadata used by the function,\nin effect starting the preparation of grading files from scratch. Should be set to `False`\n(default) except in cases of some mishaps.\n\nThis function performs the following tasks:\n\n* It reads QR codes and person numbers from exam pages. If a QR code is\nunreadable, or if a person number read does not correspond to any person number\nlisted in the gradebook file, the function will ask the user for input.\n* It adds to the gradebook file a new column `qr_code` which lists exam QR codes\nassociated with person numbers.\n* It adds a score table to each exam page (except for the cover page).\n* It assembles exam pages with score tables into new files, each file\ncontaining all copies of a given page of the exam. These files are saved in\nthe `for_grading` subdirectory of the main grading directory.\n* The function will also create a file `grading_data.json` in the main\ngrading directory, with some data which will be needed later on. Do not delete\nthis file.\n\n## 3. Grading\n\nUse some pdf annotation software to grade the pdf files. Mark the score for\neach problem in the score table. The script reading these scores is quite\nsensitive, so there is no need to cover the entire score box, a small mark\nto indicate the problem score will be fine. Do not rename the files. After\ngrading is completed, place them back in the `for_grading` subdirectory of the\nmain grading directory.\n\n\n## 4. Recording scores\n\nUse the function `ubgrade.read_scores` to read and record exam scores.\nThe signature of this function is as follows:\n\n```\nubgrade.read_scores(main_dir = None, gradebook = None, new_gradebook = None)\n```\n\n* `main_dir`: The main grading directory. If not specified the current directory will be used.\n\n* `gradebook`: The name of the gradebook file. This file needs to be located in\nthe main grading directory. If `None` it will be assumed that the file name\nis `gradebook.csv`.\n\n* `new_gradebook`: The name of the csv file where the exam scores are to be\nsaved. If `None` the `gradebok` file will be used.\n\nThis function will copy all content of the `gradebook` file (person numbers, QR codes etc.) to `new_gradebook`.\nIt will also create a column in the `new_gradebook` for each exam problem, and record problem scores.\nIf no score mark is detected on an exam page, the corresponding entry in `new_gradebook` will be `\"NONE\"`.\nIf marks in two (or more) score boxes of a score table are detected, the corresponding entry will be `\"MULTI\"`\nfollowed by a list of marked score boxes. The function also creates a column `total` with total exam scores,\nand a column `grade` which is intended to be populated with exam letter grades by the instructor. Either of these\ncolumns can be deleted if they are not to be reported to students (e.g. delete the `grade` column if there\nare no letter grades for the exam).\n\n\n## 5. Returning graded exams to students\n\n**5.1.** The csv file with exam scores can be modified as needed, by adding letter grades, columns with some bonus\nor extra credit points etc. It can be further populated with data which will be used to format emails sent to\nstudents. For example, if an email to a student is supposed to use the first name of the student (\"Dear Ann\" etc.),\nthen a column listing first names will be needed.\n\n**5.2.** Use the function `ubgrade.assemble_exams` to add score tables with problem\nscores, total scores, letter grades, and possibly other data to exam cover\npages, and to assemble the exams by student. \nThe signature of this function is as follows:\n\n```\nubgrade.assemble_exams(main_dir = None, gradebook = None, prob_labels = None, extras = None)\n```\n\n* `main_dir`: The main grading directory. If not specified the current directory will be used.\n\n* `gradebook`: The name of the gradebook file. This file needs to be located in\nthe main grading directory. If `None` it will be assumed that the file name\nis `gradebook.csv`\n\n* `prob_labels`: By default the score box of the cover page score table corresponding to a given exam page\nwill be labeled using the number of the page. For example, the score box for page 3 will be labeled \"P3\"\n(the cover page is page 0). This can be customized by assigning to prob_labels a dictionary whose keys are\nnames of columns with problem scores in the gradebook, and values are strings with labels of the\ncorresponding score boxes.\n\n* `extras`: By default the score table on the cover page will contain scores for exam problems, the total score, and the\nletter grade (provided that columns `total` and `grade` exist in the gradebook). `extras` is a dictionary which can be used to\nadd additional data to the score table. The dictionary keys are names of gradebook columns that should be used. The values are\nstrings which will used as labels of score boxes in the score table.\n\nThe pdf files produced by this function will be saved in the `graded` subdirectory of the main grading directory.\n\n**5.3.** Use the function `ubgrade.send_exams` for email graded exams to students.\nThe signature of this function is as follows:\n\n```\nubgrade.send_exams(main_dir = None, gradebook = None, template = None, send_sample = False, resend = False)\n```\n\n* `main_dir`: The main grading directory. If not specified the current directory will be used.\n\n* `gradebook`: The name of the gradebook file. This file needs to be located in\nthe main grading directory. If `None` it will be assumed that the file name\nis `gradebook.csv`\n\n* `template`: Name of a text file with the template of the text of emails.\nThis file needs to be located in the main grading directory. The text can contain `{placeholders}`, enclosed in braces.\nEach placeholder needs to be a name of a column of the gradebook.\nThe message to each student will be formatted by replacing each placeholder with the value from the corresponding column.\nIf template is `None`, an empty string will be used as the email text.\nIf the first line of the template file starts with the string `subject:` then the reminder of this line will be\nused as the subject of the message. If the file does not specify the subject, the function will prompt the user\nto provide one.\n\n* `send_sample`: Boolean. If `True` a single email message will be send with the recipient address set to be the same as the\nsender address. This can be used to test if messages are properly formatted before sending them to students.\n\n* `resend`: Boolean. Email addresses to which messages have been sent are recorded, and by default omitted when\nthe function is called again. Setting `resend` to `True` overrides this behavior, and emails are sent to every\nemail address in the gradebook.\n\nNote that this function will send emails only to students for whom graded exam files are found.\n\n\n## Version changes\n\n**0.1.8**\n- Added `skip_codes` argument to the `prepare_grading` function.\n- If the number of QR codes detected on a page is not equal to 1, or if the content of the detected QR code\n is not a valid exam code, the page is now treated as a page with missing QR code.\n- Bug fix: in the previous version the program was searching exam pages for all types of codes handled\n by the `pyzbar` library; this search is now limited to QR codes only. \n- Bug fix: the name of the gradebook file was not being passed to the function handling missing data,\n resulting in an error, fixed now.\n\n**0.1.7**\n- Bug fix.\n\n**0.1.6**\n- Added autorotation of scanned pdf files.\n- Bug fix: all files with the same exam page number were being saved in the same file for grading,\n irrespective of the root of their QR code. This is fixed now.\n- Tools for handling pages with missing QR codes or person number rewritten and moved to a separate file.\n\n**0.1.5**\n- Reliability improvements in email tools.\n\n**0.1.4**\n- Added an option to indicate which pages should be skipped from grading by setting their maximal score to 0.\n- Added an option to batch process files to prepare them for grading.\n- `qr_prefix` can be now an empty string.\n- Restructured `prepare_grading_tools` to simplify processing pages with missing data.\n- Some bug fixes.", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/bbadzioch/ubgrade", "keywords": "", "license": "GPLv3", "maintainer": "", "maintainer_email": "", "name": "ubgrade", "package_url": "https://pypi.org/project/ubgrade/", "platform": "", "project_url": "https://pypi.org/project/ubgrade/", "project_urls": { "Homepage": "https://github.com/bbadzioch/ubgrade" }, "release_url": "https://pypi.org/project/ubgrade/0.1.8/", "requires_dist": null, "requires_python": "", "summary": "Automates some tasks related to preparation and grading of exams.", "version": "0.1.8", "yanked": false, "yanked_reason": null }, "last_serial": 6725244, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "24e39f17d4e40e9cfba7686d374b4d7e", "sha256": "8abc514ba7e506c2cc057c3aa67f91a32f7a466845ee21daf81d10f4541ed90c" }, "downloads": -1, "filename": "ubgrade-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "24e39f17d4e40e9cfba7686d374b4d7e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 38926, "upload_time": "2019-10-22T20:09:22", "upload_time_iso_8601": "2019-10-22T20:09:22.550734Z", "url": "https://files.pythonhosted.org/packages/26/fc/9a1a245ef9577250f9882c6052ca9df283a8ef70d180eb457733aa350044/ubgrade-0.1.1-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "bd5561bc065193e896a93d53ab2d1210", "sha256": "2f2e630d1584de4c86e0e37ea461f4f1372b7a5dc2a71af4ca3fec1b378e2276" }, "downloads": -1, "filename": "ubgrade-0.1.1.tar.gz", "has_sig": false, "md5_digest": "bd5561bc065193e896a93d53ab2d1210", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 29309, "upload_time": "2019-10-22T20:09:25", "upload_time_iso_8601": "2019-10-22T20:09:25.614888Z", "url": "https://files.pythonhosted.org/packages/7d/4b/5fd6728c9cbd05b227a77c27be9ecb83b2cb29da04f487810bcd7e061240/ubgrade-0.1.1.tar.gz", "yanked": false, "yanked_reason": null } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "0af0439b7c41b1059745da59998e0a41", "sha256": "4d3d69afc32a509da8f4ca823e6e3e452366fccaf4f3e430c933002aff972f34" }, "downloads": -1, "filename": "ubgrade-0.1.3-py3-none-any.whl", "has_sig": false, "md5_digest": "0af0439b7c41b1059745da59998e0a41", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 64235, "upload_time": "2019-10-23T23:02:40", "upload_time_iso_8601": "2019-10-23T23:02:40.856654Z", "url": "https://files.pythonhosted.org/packages/6f/03/23f71d74cff89779c6d22f432e17c8341395cd7e13cae3bc4e5d295bb0f8/ubgrade-0.1.3-py3-none-any.whl", "yanked": false, "yanked_reason": null }, { "comment_text": "", "digests": { "md5": "0b5fb0d09e0e117d448b4994efebbcbf", "sha256": "982b4a3bf674254e891b551f9741d801376a05cb09332808830cf42827248940" }, "downloads": -1, "filename": "ubgrade-0.1.3.tar.gz", "has_sig": false, "md5_digest": "0b5fb0d09e0e117d448b4994efebbcbf", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30180, "upload_time": "2019-10-23T23:02:43", "upload_time_iso_8601": "2019-10-23T23:02:43.241338Z", "url": "https://files.pythonhosted.org/packages/44/3c/d5f7c791c59631207b2eaaa2d5e39d68bd4233435c3557ef19fa1a7bc371/ubgrade-0.1.3.tar.gz", "yanked": false, "yanked_reason": null } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "4e206e28bede31a91f953e74b2d3bdab", "sha256": "39b2ceabd58388fbec1d8a47359590c34740925cf16516aca0696f07db67c6c8" }, "downloads": -1, "filename": "ubgrade-0.1.4.tar.gz", "has_sig": false, "md5_digest": "4e206e28bede31a91f953e74b2d3bdab", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 31876, "upload_time": "2019-10-30T14:17:59", "upload_time_iso_8601": "2019-10-30T14:17:59.786178Z", "url": "https://files.pythonhosted.org/packages/44/a5/080f3f04f96019e4d46cd9397e188f40ca03c4f18f1e01d53a4fa597dd12/ubgrade-0.1.4.tar.gz", "yanked": false, "yanked_reason": null } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "8d364d22e093c1ecfd175b7c71b6e2e2", "sha256": "a9c4b31fa026f2489b2c6e9bb3063f4dc52d466bfa27733145b84677b97ea5fd" }, "downloads": -1, "filename": "ubgrade-0.1.5.tar.gz", "has_sig": false, "md5_digest": "8d364d22e093c1ecfd175b7c71b6e2e2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32017, "upload_time": "2019-11-05T02:05:22", "upload_time_iso_8601": "2019-11-05T02:05:22.096016Z", "url": "https://files.pythonhosted.org/packages/ee/49/545e61d31563ef87df25166fbd2e302eb74dc6abea12226f8f5f36e167fc/ubgrade-0.1.5.tar.gz", "yanked": false, "yanked_reason": null } ], "0.1.6": [ { "comment_text": "", "digests": { "md5": "92c946a788f35d90345801a87bcd9e77", "sha256": "a9ede77666cbb6750fc7aeadb3347cb07062c8adfe326007cd15c731b00d633b" }, "downloads": -1, "filename": "ubgrade-0.1.6.tar.gz", "has_sig": false, "md5_digest": "92c946a788f35d90345801a87bcd9e77", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34785, "upload_time": "2019-11-15T00:19:26", "upload_time_iso_8601": "2019-11-15T00:19:26.686863Z", "url": "https://files.pythonhosted.org/packages/24/36/9671bf6deae137e162504ac7353936e04485b0f636949a134563ea5bef0e/ubgrade-0.1.6.tar.gz", "yanked": false, "yanked_reason": null } ], "0.1.7": [ { "comment_text": "", "digests": { "md5": "99c8c06aaa5dd2888addb5eeb47915f3", "sha256": "79a9656f5fdbf1754a98e0ab1b836fa127f4a07902a34c77ab0744bb38481fe8" }, "downloads": -1, "filename": "ubgrade-0.1.7.tar.gz", "has_sig": false, "md5_digest": "99c8c06aaa5dd2888addb5eeb47915f3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 34802, "upload_time": "2019-11-15T17:38:04", "upload_time_iso_8601": "2019-11-15T17:38:04.662433Z", "url": "https://files.pythonhosted.org/packages/17/10/f56c0bddbeebe34c84a31c8ee49c00138211e7466b34fd921f7f61461dae/ubgrade-0.1.7.tar.gz", "yanked": false, "yanked_reason": null } ], "0.1.8": [ { "comment_text": "", "digests": { "md5": "7a2bf8cc1c8a8b72b505d97204e78902", "sha256": "5917cd625c6244f0fcd7ce9c18064f7273267ccd66628514d17e2064f6bd02a2" }, "downloads": -1, "filename": "ubgrade-0.1.8.tar.gz", "has_sig": false, "md5_digest": "7a2bf8cc1c8a8b72b505d97204e78902", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35913, "upload_time": "2020-02-29T17:54:41", "upload_time_iso_8601": "2020-02-29T17:54:41.597800Z", "url": "https://files.pythonhosted.org/packages/85/cf/f27a5787be0cee38f7d3324be5e66eb930350af3d0b891064840770510cd/ubgrade-0.1.8.tar.gz", "yanked": false, "yanked_reason": null } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7a2bf8cc1c8a8b72b505d97204e78902", "sha256": "5917cd625c6244f0fcd7ce9c18064f7273267ccd66628514d17e2064f6bd02a2" }, "downloads": -1, "filename": "ubgrade-0.1.8.tar.gz", "has_sig": false, "md5_digest": "7a2bf8cc1c8a8b72b505d97204e78902", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 35913, "upload_time": "2020-02-29T17:54:41", "upload_time_iso_8601": "2020-02-29T17:54:41.597800Z", "url": "https://files.pythonhosted.org/packages/85/cf/f27a5787be0cee38f7d3324be5e66eb930350af3d0b891064840770510cd/ubgrade-0.1.8.tar.gz", "yanked": false, "yanked_reason": null } ], "vulnerabilities": [] }