diff --git a/core/migrations/0009_auto_20180320_1922.py b/core/migrations/0009_auto_20180320_1922.py new file mode 100644 index 0000000000000000000000000000000000000000..ab2e5c48f5bf353343ed8ccf705de7e89b7f906d --- /dev/null +++ b/core/migrations/0009_auto_20180320_1922.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.3 on 2018-03-20 19:22 + +import core.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0008_auto_20180219_1712'), + ] + + operations = [ + migrations.AlterField( + model_name='studentinfo', + name='matrikel_no', + field=models.CharField(default=core.models.random_matrikel_no, max_length=30, unique=True), + ), + ] diff --git a/core/models.py b/core/models.py index b903dfda6fe253c449d2b12458b31919d52395a9..81ab511d1658e0eff7692b4147b8ba51b420f6a0 100644 --- a/core/models.py +++ b/core/models.py @@ -236,7 +236,7 @@ class StudentInfo(models.Model): editable=False) has_logged_in = models.BooleanField(default=False) matrikel_no = models.CharField(unique=True, - max_length=8, + max_length=30, default=random_matrikel_no) exam = models.ForeignKey('ExamType', on_delete=models.SET_NULL, diff --git a/core/tests/test_commands.py b/core/tests/test_commands.py index e85b8ee5f58b090755ec24e3ebd0b27801f7ed6b..97b3ea81afff6876610a63e1f9d3eb2e54903c72 100644 --- a/core/tests/test_commands.py +++ b/core/tests/test_commands.py @@ -22,7 +22,7 @@ class CommandsTestCase(TestCase): self.assertFalse(someone.is_active) def test_replaceusernames(self): - self.factory.make_student(matrikel_no=88884444, username='before') + self.factory.make_student(identifier=88884444, username='before') with tempfile.NamedTemporaryFile() as matno2username: matno2username.write(json.dumps({'88884444': 'after'}).encode()) diff --git a/util/convert.py b/util/convert.py deleted file mode 100755 index 0c45f9472e83c59c0d2e752097fab37ae5301ae9..0000000000000000000000000000000000000000 --- a/util/convert.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/local/bin/python3 -""" a simple script that converts ilias exam output to readable json - -The json output will look like this: -{ - "max.mustermann": { <<--- OR all uppercase letter of the name + username/matrikel_no # noqa: E501 - "matrikel_no": "12345678", - "name": "Mustermann, Max", - "task_list": { - "[task_id_1]": "print Hello World!", - ...., - "[task_id_n]": "#include <stdio.h> etc." - } - }, - ... ans so on -} - -usage: convert.py [-h] [-u USERNAMES] [-n NUMBER_OF_TASKS] INFILE OUTFILE - -positional arguments: - INFILE Ilias exam data - OUTFILE Where to write the final file - -optional arguments: - -h, --help show this help message and exit - -u USERNAMES, --usernames USERNAMES - a json dict matno -> email - -n NUMBER_OF_TASKS, --NUMBER_OF_TASKS NUMBER_OF_TASKS - Where to write the final file - - -Author: Jan Maximilian Michal -Date: 30 March 2017 -""" - -import argparse -import json -import os -import re -import urllib.parse -from collections import defaultdict, namedtuple - -from xlrd import open_workbook - -parser = argparse.ArgumentParser() -parser.add_argument('INFILE', help='Ilias exam data') -parser.add_argument('OUTFILE', help='Where to write the final file') -parser.add_argument('-u', '--usernames', help='a json dict matno -> email') -parser.add_argument( - '-n', '--NUMBER_OF_TASKS', - default=0, # don't check - metavar='NUMBER_OF_TASKS', - type=int, - help='Where to write the final file') - - -# one user has one submission (code) per task -# yes, I know it is possible to name match groups via (?P<name>) but -# I like this solution better since it gets the job done nicely -user_t = namedtuple('user_head', 'name matrikel_no') - -# one task has a title and id and hpfly code -task_head_re = re.compile(r'^Quellcode Frage (?P<title>.*?) ?(\d{8})?$') - -# nor parsing the weird mat no -matno_re = re.compile(r'^(?P<matrikel_no>\d{8})-(\d+)-(\d+)$') - - -def converter(infile, usernames=None, number_of_tasks=0,): - - # Modify these iterators in order to change extraction behaviour - - def sheet_iter_meta(sheet): - """ yield first and second col entry as tuple of (name, matnr) """ - for row in (sheet.row(i) for i in range(1, sheet.nrows)): - match = re.search(matno_re, row[1].value) - if match: - yield row[0].value, match.group('matrikel_no') - - def sheet_iter_data(sheet): - """ yields all source code titel and code tuples """ - def row(i): - return sheet.row(i) - for top, low in ((row(i), row(i + 1)) for i in range(sheet.nrows - 1)): - if any(map(lambda c: c.ctype, top)) and 'Quell' in top[0].value: - yield (' '.join(c.value for c in top), - ' '.join(c.value for c in low)) - - # meta sheet contains ilias names usernames etc - data contains code - meta, *data = open_workbook(infile, open(os.devnull, 'w')).sheets() - - # nice! - name2mat = dict(sheet_iter_meta(meta)) - assert len(name2mat) == len(data), f'{len(name2mat)} names != {len(data)} sheets' # noqa - - # from xls to lists and namedtuples - # [ [user0, task0_h, code0, ..., taskn, coden ], ..., [...] ] - root = [] - for user, sheet in zip(sheet_iter_meta(meta), data): - root.append([user_t(*user)]) - for task, code in sheet_iter_data(sheet): - task = re.search(task_head_re, task) - root[-1].append(task.group('title')) - root[-1].append(urllib.parse.unquote(code).strip()) - - if number_of_tasks: - for (user, *task_list) in sorted(root, key=lambda u: u[0].name): - assert len(task_list) == number_of_tasks * 2 - - mat_to_email = defaultdict(str) - if usernames: - with open(usernames) as data: - mat_to_email.update(json.JSONDecoder().decode(data.read())) - - def get_username(user): - if name2mat[user.name] in mat_to_email: - return mat_to_email[name2mat[user.name]].split('@')[0] - return ''.join(filter(str.isupper, user.name)) + name2mat[user.name] - - usernames = {user.name: get_username(user) for (user, *_) in root} - - # form list to json_like via comprehension - # the format {userinitials + matrikel_no : {name:, matrikel_no:, tasklist: - # {id:, ..., id:}}} - return { - usernames[user.name]: { - 'fullname': user.name, - 'email': mat_to_email[name2mat[user.name]], - 'matrikel_no': name2mat[user.name], - 'submissions': [ - { - "type": task, - "code": code, - "tests": {}, - } for task, code in zip(task_list[::2], task_list[1::2]) - ] - } for (user, *task_list) in sorted(root, key=lambda u: u[0].name) - } - - -def write_to_file(json_dict, outfile): - # just encode python style - with open(outfile, "w") as out: - json.dump(json_dict, out, indent=2) - - print(f"Wrote data to {outfile}. Done.") - - -def main(): - args = parser.parse_args() - json_dict = converter(args.INFILE, args.usernames, args.NUMBER_OF_TASKS) - write_to_file(json_dict, args.OUTFILE) - - -if __name__ == '__main__': - SCRIPT = True - main() diff --git a/util/factories.py b/util/factories.py index db42ebcbea881c8b7076f31cc4dbe660bfa4c92b..da49196e22bb4f0fdb84a7efc6629626d15fc016 100644 --- a/util/factories.py +++ b/util/factories.py @@ -89,14 +89,14 @@ class GradyUserFactory: return user - def make_student(self, username=None, matrikel_no=None, + def make_student(self, username=None, identifier=None, exam=None, submissions=None, **kwargs): """ Creates a student. Defaults can be passed via kwargs like in relation managers objects.update method. """ user = self._make_base_user(username, 'Student', **kwargs) student_info = StudentInfo.objects.get_or_create(user=user)[0] - if matrikel_no: - student_info.matrikel_no = matrikel_no + if identifier: + student_info.matrikel_no = identifier if exam: student_info.exam = exam student_info.save() diff --git a/util/importer.py b/util/importer.py index 80e6fed1414b2bee8f3dfd07c4e13ae3ca01b10e..b5d2e1733f2c05316ad01a831b8a8a1972aa4121 100644 --- a/util/importer.py +++ b/util/importer.py @@ -6,7 +6,6 @@ from typing import Callable from django.db import transaction -import util.convert import util.processing from core.models import ExamType, Feedback, Submission, SubmissionType, Test from core.models import UserAccount as User @@ -163,19 +162,6 @@ def call_loader(func: Callable) -> None: info(f'{func.__name__} is done.') -def do_convert_xls(): - - ans = i('''Do you want to convert the ILIAS .xls output to .json?''', YES) - if not ans: - return - - infile = i('Please provide the path to the .xls file', is_file=True) - outfile = i('Where should the output go?', 'submissons.json') - - json_dict = util.convert.converter(infile) - util.convert.write_to_file(json_dict, outfile) - - def do_load_submission_types(): print( @@ -349,7 +335,7 @@ def do_load_submissions(): file = i('Get me the file with all the submissions', 'submissions.json', is_file=True) - exam = {} + exam_obj = {} if ExamType.objects.all() and \ i('Do you want to add module/exam information?', NO): exam_query_set = ExamType.objects.all() @@ -358,18 +344,17 @@ def do_load_submissions(): print(f'\t[{j}] {exam_type.module_reference}') print() - exam = i('Choose wisely') - exam = {'exam': exam_query_set[int(exam)]} + exam_prompt_key = i('Choose wisely') + exam_obj = {'exam': exam_query_set[int(exam_prompt_key)]} - with open(file) as submission_file: - submissions = json.JSONDecoder().decode(submission_file.read()) + with open(file) as exam_data_file: + exam_data = json.JSONDecoder().decode(exam_data_file.read()) - for username, data in submissions.items(): - student_obj = user_factory.make_student(username, - **exam, - **data).student + for student in exam_data['students']: + student_obj = user_factory.make_student(**exam_obj, + **student).student - for submission_obj in data['submissions']: + for submission_obj in student['submissions']: add_submission(student_obj, **submission_obj) @@ -396,7 +381,6 @@ def do_load_reviewer(): call_order = ( - do_convert_xls, do_load_submission_types, do_load_module_descriptions, do_preprocess_submissions,