From 5d01c744405c425a0599513ec2cdb3a49a8a0433 Mon Sep 17 00:00:00 2001 From: Tim Schubert Date: Thu, 20 Sep 2018 15:53:40 +0200 Subject: [PATCH] Create subgroup for enrolled students so when creating projects we do not need the exported list of students and can use the subgroup instead. --- README.md | 2 +- src/abgabesystem/commands.py | 15 ++++-- src/abgabesystem/projects.py | 90 ++++++++++++++++++------------------ src/abgabesystem/students.py | 59 ++++++++++++++++++++++- src/bin/abgabesystem | 8 ++-- 5 files changed, 117 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 7605968..1f3c883 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Operations 1 and 2 require super user privileges to the API. The rest don't. 4. Set up the project for the example solutions and the student projects. If you have pre-existing example solutions place them in `/solutions/solutions`. ``` -# abgabesystem projects -c -d -s +# abgabesystem projects -c -d ``` 5. Add all administrative users (e.g. users supervising the course or checking homework solutions) to the group of the course. diff --git a/src/abgabesystem/commands.py b/src/abgabesystem/commands.py index 3406c3d..a72efc9 100644 --- a/src/abgabesystem/commands.py +++ b/src/abgabesystem/commands.py @@ -1,19 +1,24 @@ import os import subprocess -import subprocess +import logging as log -from .students import Student, create_user, get_students +from .students import Student, create_user, get_students, enroll_student, get_student_group from .projects import create_tag, setup_course from gitlab.exceptions import GitlabCreateError, GitlabGetError -def create_users(gl, args): +def enroll_students(gl, args): """Creates Gitlab users from exported students list """ + + student_group = get_student_group(gl, args.course) + with open(args.students, encoding='iso8859') as students_csv: for student in Student.from_csv(students_csv): try: - create_user(gl, student, args.ldap_base, args.ldap_provider) + user = create_user(gl, student, args.ldap_base, args.ldap_provider) + # TODO this is ugly, should be group of course, but python-gitlab does not cache the query + enroll_student(gl, user, student_group) except GitlabCreateError: log.warn('Failed to create user: %s' % student.user) @@ -85,7 +90,7 @@ def course(gl, args): """Creates the group for the course """ try: - group = gl.groups.create({ + gl.groups.create({ 'name': args.course, 'path': args.course, 'visibility': 'internal', diff --git a/src/abgabesystem/projects.py b/src/abgabesystem/projects.py index 4537dc3..2aed458 100644 --- a/src/abgabesystem/projects.py +++ b/src/abgabesystem/projects.py @@ -1,4 +1,10 @@ +import logging as log + +from gitlab import DEVELOPER_ACCESS from gitlab.exceptions import GitlabError, GitlabCreateError +from .students import enrolled_students +from .course import InvalidCourse + def create_tag(project, tag, ref): """Creates protected tag on ref @@ -60,7 +66,7 @@ def create_project(gl, group, user, reference, deploy_key): try: subgroup.members.create({ 'user_id': user.id, - 'access_level': gitlab.DEVELOPER_ACCESS, + 'access_level': DEVELOPER_ACCESS, }) except GitlabError: log.warning('Failed to add student %s to its own group' % user.username) @@ -71,55 +77,47 @@ def create_project(gl, group, user, reference, deploy_key): log.warning(e.error_message) -def setup_course(gl, group, students_csv, deploy_key): +def create_reference_solution(gl, namespace): + reference_project = gl.projects.create({ + 'name': 'solutions', + 'namespace_id': namespace, + 'visibility': 'internal', + }) + reference_project.commits.create({ + 'branch': 'master', + 'commit_message': 'Initial commit', + 'actions': [ + { + 'action': 'create', + 'file_path': 'README.md', + 'content': 'Example solutions go here', + }, + ] + }) + + return reference_project + + +def setup_projects(gl, course, deploy_key): """Sets up the internal structure for the group for use with the course """ - solution = None - reference_project = None - try: - solution = gl.groups.create({ - 'name': 'solutions', - 'path': 'solutions', - 'parent_id': group.id, - 'visibility': 'internal', - }) - except GitlabCreateError as e: - log.info('Failed to create solutions group. %s' % e.error_message) - solutions = group.subgroups.list(search='solutions') - if len(solutions) > 0 and solutions[0].name == 'solutions': - solution = gl.groups.get(solutions[0].id, lazy=True) - else: - raise(GitlabCreateError(error_message='Failed to setup solutions subgroup')) + solutions = None + solutions_groups = course.subgroups.list(search='solutions') + for group in solutions_groups: + if group.name == 'solutions': + solutions = group - try: - reference_project = gl.projects.create({ - 'name': 'solutions', - 'namespace_id': solution.id, - 'visibility': 'internal', - }) - reference_project.commits.create({ - 'branch': 'master', - 'commit_message': 'Initial commit', - 'actions': [ - { - 'action': 'create', - 'file_path': 'README.md', - 'content': 'Example solutions go here', - }, - ] - }) - except GitlabCreateError as e: - log.info('Failed to setup group structure. %s' % e.error_message) - projects = solution.projects.list(search='solutions') - if len(projects) > 0 and projects[0].name == 'solutions': - reference_project = gl.projects.get(projects[0].id) - else: - raise(GitlabCreateError(error_message='Failed to setup reference solutions')) + if solutions is None: + raise InvalidCourse("No solutions subgroup") - if solution is None or reference_project is None: - raise(GitlabCreateError(error_message='Failed to setup course')) + reference_projects = solutions.projects.list(search='solutions') + for project in reference_projects: + if project.name == 'solutions': + reference_project = gl.projects.get(project.id) - for user in get_students(gl, students_csv): - create_project(gl, solution, user, reference_project, deploy_key) + if reference_project is None: + reference_project = create_reference_solution(gl, solutions.id) + for user in enrolled_students(gl, course): + create_project(gl, solutions, user, reference_project, deploy_key) diff --git a/src/abgabesystem/students.py b/src/abgabesystem/students.py index 6c8e43c..746c028 100644 --- a/src/abgabesystem/students.py +++ b/src/abgabesystem/students.py @@ -1,4 +1,13 @@ import csv +import secrets + + +class MissingStudentsGroup(Exception): + pass + + +class MissingCourseGroup(Exception): + pass class Student(): @@ -24,7 +33,7 @@ class Student(): + ' ' + line['Nachname'], line['Gruppe']) -def get_students(gl, students_csv): +def get_students_csv(gl, students_csv): """Returns already existing GitLab users for students from provided CSV file that have an account. """ @@ -34,6 +43,24 @@ def get_students(gl, students_csv): yield users[0] +def enrolled_students(gl, course): + """Returns the students enrolled in the course + """ + + students = None + for group in course.subgroups.list(search='students'): + if group.name == 'students': + students = group + + if students is None: + raise MissingStudentsGroup() + + # get all members excluding inherited members + students = gl.groups.get(students.id) + for member in students.members.list(): + yield gl.users.get(member.id) + + def create_user(gl, student, ldap_base, ldap_provider): """Creates a GitLab user account student. Requires admin privileges. @@ -52,3 +79,33 @@ def create_user(gl, student, ldap_base, ldap_provider): return user + +def get_student_group(gl, course_name): + """Gets the `students` subgroup for the course + """ + + course = None + for g in gl.groups.list(search=course_name): + if g.name == course_name: + course = g + + if course is None: + raise MissingCourseGroup() + + students_group = None + + for g in course.subgroups.list(search='students'): + if g.name == 'students': + students_group = gl.groups.get(g.id) + + if students_group is None: + raise MissingStudentsGroup() + + return students_group + + +def enroll_student(gl, user, group): + """Adds a student to the course + """ + pass + diff --git a/src/bin/abgabesystem b/src/bin/abgabesystem index 315e882..59abf7d 100755 --- a/src/bin/abgabesystem +++ b/src/bin/abgabesystem @@ -4,7 +4,7 @@ import gitlab import argparse import logging as log -from abgabesystem.commands import create_users, projects, deadline, plagiates, course +from abgabesystem.commands import enroll_students, projects, deadline, plagiates, course if __name__ == '__main__': @@ -17,9 +17,10 @@ if __name__ == '__main__': user_parser = subparsers.add_parser( 'users', - help='Creates users from LDAP') - user_parser.set_defaults(func=create_users) + help='Creates users and enrolls them in the course') + user_parser.set_defaults(func=enroll_students) user_parser.add_argument('-s', '--students', dest='students') + user_parser.add_argument('-c', '--course', dest='course') user_parser.add_argument('-b', '--ldap-base', dest='ldap_base') user_parser.add_argument('-p', '--ldap-provider', dest='ldap_provider') @@ -35,7 +36,6 @@ if __name__ == '__main__': projects_parser.set_defaults(func=projects) projects_parser.add_argument('-c', '--course', dest='course') projects_parser.add_argument('-d', '--deploy-key', dest='deploy_key') - projects_parser.add_argument('-s', '--students', dest='students') deadline_parser = subparsers.add_parser( 'deadline',