Create subgroup for enrolled students so when creating projects we do not need the exported list of students and can use the subgroup instead.

This commit is contained in:
Tim Schubert 2018-09-20 15:53:40 +02:00
parent 01bfb44c73
commit 5d01c74440
5 changed files with 117 additions and 57 deletions

View file

@ -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 `<some_course>/solutions/solutions`. 4. Set up the project for the example solutions and the student projects. If you have pre-existing example solutions place them in `<some_course>/solutions/solutions`.
``` ```
# abgabesystem projects -c <some_course> -d <deploy key> -s <students.csv> # abgabesystem projects -c <some_course> -d <deploy key>
``` ```
5. Add all administrative users (e.g. users supervising the course or checking homework solutions) to the group of the course. 5. Add all administrative users (e.g. users supervising the course or checking homework solutions) to the group of the course.

View file

@ -1,19 +1,24 @@
import os import os
import subprocess 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 .projects import create_tag, setup_course
from gitlab.exceptions import GitlabCreateError, GitlabGetError from gitlab.exceptions import GitlabCreateError, GitlabGetError
def create_users(gl, args): def enroll_students(gl, args):
"""Creates Gitlab users from exported students list """Creates Gitlab users from exported students list
""" """
student_group = get_student_group(gl, args.course)
with open(args.students, encoding='iso8859') as students_csv: with open(args.students, encoding='iso8859') as students_csv:
for student in Student.from_csv(students_csv): for student in Student.from_csv(students_csv):
try: 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: except GitlabCreateError:
log.warn('Failed to create user: %s' % student.user) log.warn('Failed to create user: %s' % student.user)
@ -85,7 +90,7 @@ def course(gl, args):
"""Creates the group for the course """Creates the group for the course
""" """
try: try:
group = gl.groups.create({ gl.groups.create({
'name': args.course, 'name': args.course,
'path': args.course, 'path': args.course,
'visibility': 'internal', 'visibility': 'internal',

View file

@ -1,4 +1,10 @@
import logging as log
from gitlab import DEVELOPER_ACCESS
from gitlab.exceptions import GitlabError, GitlabCreateError from gitlab.exceptions import GitlabError, GitlabCreateError
from .students import enrolled_students
from .course import InvalidCourse
def create_tag(project, tag, ref): def create_tag(project, tag, ref):
"""Creates protected tag on ref """Creates protected tag on ref
@ -60,7 +66,7 @@ def create_project(gl, group, user, reference, deploy_key):
try: try:
subgroup.members.create({ subgroup.members.create({
'user_id': user.id, 'user_id': user.id,
'access_level': gitlab.DEVELOPER_ACCESS, 'access_level': DEVELOPER_ACCESS,
}) })
except GitlabError: except GitlabError:
log.warning('Failed to add student %s to its own group' % user.username) 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) 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 """Sets up the internal structure for the group for use with the course
""" """
solution = None
reference_project = None
try: solutions = None
solution = gl.groups.create({ solutions_groups = course.subgroups.list(search='solutions')
'name': 'solutions', for group in solutions_groups:
'path': 'solutions', if group.name == 'solutions':
'parent_id': group.id, solutions = group
'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'))
try: if solutions is None:
reference_project = gl.projects.create({ raise InvalidCourse("No solutions subgroup")
'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 solution is None or reference_project is None: reference_projects = solutions.projects.list(search='solutions')
raise(GitlabCreateError(error_message='Failed to setup course')) for project in reference_projects:
if project.name == 'solutions':
reference_project = gl.projects.get(project.id)
for user in get_students(gl, students_csv): if reference_project is None:
create_project(gl, solution, user, reference_project, deploy_key) reference_project = create_reference_solution(gl, solutions.id)
for user in enrolled_students(gl, course):
create_project(gl, solutions, user, reference_project, deploy_key)

View file

@ -1,4 +1,13 @@
import csv import csv
import secrets
class MissingStudentsGroup(Exception):
pass
class MissingCourseGroup(Exception):
pass
class Student(): class Student():
@ -24,7 +33,7 @@ class Student():
+ ' ' + line['Nachname'], line['Gruppe']) + ' ' + 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. """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] 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): def create_user(gl, student, ldap_base, ldap_provider):
"""Creates a GitLab user account student. """Creates a GitLab user account student.
Requires admin privileges. Requires admin privileges.
@ -52,3 +79,33 @@ def create_user(gl, student, ldap_base, ldap_provider):
return user 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

View file

@ -4,7 +4,7 @@ import gitlab
import argparse import argparse
import logging as log 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__': if __name__ == '__main__':
@ -17,9 +17,10 @@ if __name__ == '__main__':
user_parser = subparsers.add_parser( user_parser = subparsers.add_parser(
'users', 'users',
help='Creates users from LDAP') help='Creates users and enrolls them in the course')
user_parser.set_defaults(func=create_users) user_parser.set_defaults(func=enroll_students)
user_parser.add_argument('-s', '--students', dest='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('-b', '--ldap-base', dest='ldap_base')
user_parser.add_argument('-p', '--ldap-provider', dest='ldap_provider') 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.set_defaults(func=projects)
projects_parser.add_argument('-c', '--course', dest='course') projects_parser.add_argument('-c', '--course', dest='course')
projects_parser.add_argument('-d', '--deploy-key', dest='deploy_key') projects_parser.add_argument('-d', '--deploy-key', dest='deploy_key')
projects_parser.add_argument('-s', '--students', dest='students')
deadline_parser = subparsers.add_parser( deadline_parser = subparsers.add_parser(
'deadline', 'deadline',