From 12e9f05a503033dacc942a22eab94497233b33d2 Mon Sep 17 00:00:00 2001 From: janmax <j.michal@stud.uni-goettingen.de> Date: Thu, 4 Jan 2018 16:39:01 +0100 Subject: [PATCH] Migrated the commands in delbert.py into Django commands --- .../management/commands/extractsubmissions.py | 12 ++ core/management/commands/importer.py | 10 ++ core/management/commands/maketestdata.py | 10 ++ core/management/commands/replaceusernames.py | 31 ++++ .../commands/setstudentpasswords.py | 42 +++++ core/management/commands/usermod.py | 37 +++++ core/tests/test_commands.py | 35 +++++ delbert.py | 143 ------------------ util/factories.py | 13 +- 9 files changed, 185 insertions(+), 148 deletions(-) create mode 100644 core/management/commands/extractsubmissions.py create mode 100644 core/management/commands/importer.py create mode 100644 core/management/commands/maketestdata.py create mode 100644 core/management/commands/replaceusernames.py create mode 100644 core/management/commands/setstudentpasswords.py create mode 100644 core/management/commands/usermod.py create mode 100644 core/tests/test_commands.py delete mode 100755 delbert.py diff --git a/core/management/commands/extractsubmissions.py b/core/management/commands/extractsubmissions.py new file mode 100644 index 00000000..ac8db1d5 --- /dev/null +++ b/core/management/commands/extractsubmissions.py @@ -0,0 +1,12 @@ +from django.core.management.base import BaseCommand +from core import models + + +class Command(BaseCommand): + help = 'Extract all submissions from this instance' + + def handle(self, *args, **kwargs): + for submission in models.Submission.objects.filter( + feedback__isnull=False).order_by('type'): + print(submission.feedback.score, repr(submission.text), + file=open(str(submission.type).replace(' ', '_'), 'a')) diff --git a/core/management/commands/importer.py b/core/management/commands/importer.py new file mode 100644 index 00000000..1d0bd87c --- /dev/null +++ b/core/management/commands/importer.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand + +import util.importer + + +class Command(BaseCommand): + help = 'Start the Grady command line importer' + + def handle(self, *args, **kwargs): + util.importer.start() diff --git a/core/management/commands/maketestdata.py b/core/management/commands/maketestdata.py new file mode 100644 index 00000000..075cf243 --- /dev/null +++ b/core/management/commands/maketestdata.py @@ -0,0 +1,10 @@ +from django.core.management.base import BaseCommand + +from util.factories import init_test_instance + + +class Command(BaseCommand): + help = 'Creates some initial test data for the application' + + def handle(self, *args, **options): + init_test_instance() diff --git a/core/management/commands/replaceusernames.py b/core/management/commands/replaceusernames.py new file mode 100644 index 00000000..d496ee95 --- /dev/null +++ b/core/management/commands/replaceusernames.py @@ -0,0 +1,31 @@ +import argparse +import json +import sys + +from django.core.management.base import BaseCommand + +from core.models import Student + + +class Command(BaseCommand): + help = ('replaces all usernames based on a ' + 'matrikel_no -> new_name dict (input should be JSON)') + + def add_arguments(self, parser): + parser.add_argument( + 'matno2username_dict', + help='the mapping as a JSON file', + default=sys.stdin, + type=argparse.FileType('r') + ) + + def _handle(self, matno2username_dict, **kwargs): + matno2username = json.JSONDecoder().decode(matno2username_dict.read()) + for student in Student.objects.all(): + if student.matrikel_no in matno2username: + new_name = matno2username[student.matrikel_no] + student.user.username = new_name + student.user.save() + + def handle(self, *args, **options): + self._handle(*args, **options) diff --git a/core/management/commands/setstudentpasswords.py b/core/management/commands/setstudentpasswords.py new file mode 100644 index 00000000..0b63a275 --- /dev/null +++ b/core/management/commands/setstudentpasswords.py @@ -0,0 +1,42 @@ +import csv +import secrets +import sys + +from django.core.management.base import BaseCommand + +from core.models import Student + + +class Command(BaseCommand): + help = ('All student passwords will be changed' + 'and a list of these password will be printed') + + def add_arguments(self, parser): + parser.add_argument( + 'instance', + help="Name of the instance that generated the passwords" + ) + + def _handle(self, *args, output=sys.stdout, instance="", **kwargs): + with open('/usr/share/dict/words') as words: + choose_from = list({word.strip().lower() + for word in words if 5 < len(word) < 8}) + + writer = csv.writer(output) + writer.writerow( + ['Name', 'Matrikel', 'Username', 'password', 'instance']) + + for student in Student.objects.all(): + password = ''.join(secrets.choice(choose_from) for _ in range(3)) + + student.user.set_password(password) + student.user.save() + + if not student.user.fullname: + student.user.fullname = '__no_name__' + + writer.writerow([student.user.fullname, student.matrikel_no, + student.user.username, password, instance]) + + def handle(self, *args, **options): + self._handle(*args, **options) diff --git a/core/management/commands/usermod.py b/core/management/commands/usermod.py new file mode 100644 index 00000000..14a95621 --- /dev/null +++ b/core/management/commands/usermod.py @@ -0,0 +1,37 @@ +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = 'All user accounts will be disabled' + + def add_arguments(self, parser): + parser.add_argument( + 'switch', + choices=('enable', 'disable'), + default='enable', + help='enable all users (enable) or disable all (disable)' + ) + filter_group = parser.add_mutually_exclusive_group() + filter_group.add_argument( + '--exclude', + default=(), + nargs='+', + help='Provide all users you want to exclude from the operation' + ) + filter_group.add_argument( + '--include', + help=('Provide users you want to operate on' + 'Everything else is untouched'), + nargs='+', + default=()) + + def handle(self, switch, exclude=None, include=None, *args, **kwargs): + if include: + for user in get_user_model().objects.filter(username__in=include): + user.is_active = switch == 'enable' + user.save() + else: # this includes nothing set + for user in get_user_model().objects.exclude(username__in=exclude): + user.is_active = switch == 'enable' + user.save() diff --git a/core/tests/test_commands.py b/core/tests/test_commands.py new file mode 100644 index 00000000..8286c8e1 --- /dev/null +++ b/core/tests/test_commands.py @@ -0,0 +1,35 @@ +from django.core.management import call_command +from django.contrib.auth import get_user_model +from django.test import TestCase + +from util.factories import GradyUserFactory + +from core.models import Student +import tempfile +import json + + +class CommandsTestCase(TestCase): + + factory = GradyUserFactory() + + def test_usermod(self): + self.factory.make_tutor(username='otto') + args = ['disable'] + opts = {'include': ('otto',)} + call_command('usermod', *args, **opts) + + someone = get_user_model().objects.get(username='otto') + self.assertFalse(someone.is_active) + + def test_replaceusernames(self): + self.factory.make_student(matrikel_no=88884444, username='before') + + with tempfile.NamedTemporaryFile() as matno2username: + matno2username.write(json.dumps({'88884444': 'after'}).encode()) + matno2username.flush() + args = [matno2username.name] + call_command('replaceusernames', *args, **{}) + + student = Student.objects.get(matrikel_no=88884444) + self.assertEqual('after', student.user.username) diff --git a/delbert.py b/delbert.py deleted file mode 100755 index d3526487..00000000 --- a/delbert.py +++ /dev/null @@ -1,143 +0,0 @@ -import argparse -import csv -import json -import os -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'grady.settings') -import secrets -import sys - -import django -django.setup() -from django.contrib.auth.models import User - -import util.importer -from core.models import Student, Submission - - -unused_variable = [] - - -def parseme(): - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(dest="command") - - parser.add_argument( - '-o', '--output', - help='Where the output goes (not info messages)', - default=sys.stdout, - type=argparse.FileType(mode='r'), - ) - - ### parser for printing out new passwordlists ### - passwordlist = subparsers.add_parser( - 'passwordlist', - help='all student passwords will be changed and a list of these password will be printed' - ) - passwordlist.add_argument( - 'instance', - default='', - help='name of the instance that generated the passwords' - ) - - ### parser for replacing usernames ### - replaceusernames = subparsers.add_parser( - 'replaceusernames', - help='replaces all usernames based on a matrikel_no -> new_name dict (input should be JSON)' - ) - replaceusernames.add_argument( - 'matno2username_dict', - help='the mapping as a JSON file', - default=sys.stdin, - type=argparse.FileType('r') - ) - - ### parser for enabling or disabling users ### - enableusers = subparsers.add_parser( - 'enableusers', - help='All user accounts will be disabled' - ) - enableusers.add_argument( - 'switch', - choices=('on', 'off'), - default='on', - help='enable all users (on) or disable all (off)' - ) - filter_group = enableusers.add_mutually_exclusive_group() - filter_group.add_argument( - '-e', '--exclude', - default=(), - nargs='+', - help='give exceptions by username in a comma separated list' - ) - filter_group.add_argument( - '-i', '--include', - help='only apply to these users', - nargs='+', - default=()) - - ### parser for extracting submissions ### - subparsers.add_parser('extractsubmissions') - - ### parser for extracting submissions ### - subparsers.add_parser('importer') - - return parser.parse_args() - - -def handle_passwordlist(output=sys.stdout, instance="", **kwargs): - with open('/usr/share/dict/words') as words: - choose_from = list({word.strip().lower() - for word in words if 5 < len(word) < 8}) - - writer = csv.writer(output) - writer.writerow(['Name', 'Matrikel', 'Username', 'password', 'instance']) - - for student in Student.objects.all(): - password = ''.join(secrets.choice(choose_from) for _ in range(3)) - - student.user.set_password(password) - student.user.save() - - writer.writerow([student.name, student.matrikel_no, - student.user.username, password, instance]) - - -def handle_enableusers(switch, exclude, include, **kwargs): - - if include: - for user in User.objects.filter(username__in=include): - user.is_active = switch == 'on' - user.save() - else: # this includes nothing set - for user in User.objects.exclude(username__in=exclude): - user.is_active = switch == 'on' - user.save() - - -def handle_replaceusernames(matno2username_dict, **kwargs): - matno2username = json.JSONDecoder().decode(matno2username_dict.read()) - for student in Student.objects.all(): - if student.matrikel_no in matno2username: - new_name = matno2username[student.matrikel_no] - student.user.username = new_name - student.user.save() - - -def handle_extractsubmissions(output, **kwargs): - for submission in Submission.objects.filter(feedback__isnull=False).order_by('type'): - print(submission.feedback.score, repr(submission.text), - file=open(str(submission.type).replace(' ', '_'), 'a')) - - -def handle_importer(**kwargs): - util.importer.start() - - -def main(): - args = parseme() - if args.command: - globals()['handle_' + args.command](**vars(args)) - - -if __name__ == '__main__': - main() diff --git a/util/factories.py b/util/factories.py index 8888c043..63da6ecf 100644 --- a/util/factories.py +++ b/util/factories.py @@ -45,12 +45,13 @@ class GradyUserFactory: def _get_random_name(prefix='', suffix='', k=1): return ''.join((prefix, get_random_password(k), suffix)) - def _make_base_user(self, username, groupname, password=None, store_pw=False, **kwargs): + def _make_base_user(self, username, groupname, password=None, + store_pw=False, **kwargs): """ This is a specific wrapper for the django update_or_create method of objects. * A new user is created and password and group are set accordingly - * If the user was there before password is NOT change but group is. A - user must only have one group. + * If the user was there before password is NOT change but group is. + * A user must only have one group. Returns: (User object, str): The user object that was added to the group and @@ -96,7 +97,9 @@ class GradyUserFactory: return generic_user - def make_student(self, username=None, matrikel_no=None, exam=None, **kwargs): + def make_student(self, username=None, + matrikel_no=None, + exam=None, **kwargs): """ Creates a student. Defaults can be passed via kwargs like in relation managers objects.update method. """ user = self._make_user_generic(username, STUDENTS, **kwargs) @@ -125,7 +128,7 @@ def make_exams(exams=[], **kwargs): def make_submission_types(submission_types=[], **kwargs): return [SubmissionType.objects.get_or_create( name=submission_type['name'], defaults=submission_type)[0] - for submission_type in submission_types] + for submission_type in submission_types] def make_students(students=[], **kwargs): -- GitLab