diff --git a/bbcli/cli.py b/bbcli/cli.py index 0dfb4aa1fbf784234a9e02271568990bf0722961..5c7c4717d5cc3fb4fe466c81adac585c79462b21 100644 --- a/bbcli/cli.py +++ b/bbcli/cli.py @@ -8,7 +8,7 @@ from dotenv import load_dotenv from bbcli import check_valid_date import click -from bbcli.commands.courses import list_courses +from bbcli.commands.courses import list_course_users, list_courses from bbcli.commands.announcements import list_announcements, create_announcement, delete_announcement, update_announcement from bbcli.commands.contents import create_assignment_from_contents, create_courselink, create_folder, delete_content, list_contents, create_document, create_file, create_web_link, update_content, upload_attachment, get_content from bbcli.commands.assignments import get_assignments, submit_attempt, grade_assignment, get_attempts, get_attempt, submit_draft, update_attempt, submit_draft, create_assignment @@ -114,6 +114,19 @@ def courses(ctx): courses.add_command(list_courses) +""" +COURSE USER COMMANDS ENTRY POINT +""" + +@courses.group(name='users') +@click.pass_context +def course_users(ctx): + """ + Commands for listing users of a course + """ + +course_users.add_command(list_course_users) + """ ANNOUNCEMENT COMMANDS ENTRY POINT @@ -202,11 +215,7 @@ def create(ctx): """ Commands for creating different types of content types in blackboard """ - authenticate_user() - load_dotenv() - session = initiate_session() - ctx.obj['SESSION'] = session - + pass create.add_command(create_document) create.add_command(create_file) diff --git a/bbcli/commands/courses.py b/bbcli/commands/courses.py index 39f063c33b7b58b064249095cf1cc617c6ac86ce..98280681b85c1037b70e1237f7436a0c8de9e93c 100644 --- a/bbcli/commands/courses.py +++ b/bbcli/commands/courses.py @@ -28,3 +28,18 @@ def list_courses(ctx, course_id=None, show_all=False): response = courses_service.list_courses( session=ctx.obj['SESSION'], user_name=user_name) course_view.print_courses(response) + + +@click.command(name='list', help='List all users from specific course.') +@click.option('-c', '--course', 'course_id', required=True, type=str, help='[COURSE ID] Get information about a specific course') +@click.option('-j', '--json', is_flag=True, help='Display data in json format') +@click.option('--csv', is_flag=True, help='Save data to a csv file') +@click.pass_context +@list_exception_handler +def list_course_users(ctx, course_id, json, csv): + response = None + response = courses_service.list_course_users(ctx.obj['SESSION'], course_id) + if csv: + course_view.save_data_to_csv(response) + else: + course_view.print_course_users(response, json) \ No newline at end of file diff --git a/bbcli/services/courses_service.py b/bbcli/services/courses_service.py index 2f5454bffc9ad148372df9fb9d9a79aa012107e4..321e9c3f4a63e39333ff5966ea6cfbca84cc295d 100644 --- a/bbcli/services/courses_service.py +++ b/bbcli/services/courses_service.py @@ -47,6 +47,17 @@ def list_course(session: requests.Session, course_id: str) -> Any: response.raise_for_status() return json.loads(response.text) +# TODO: add threading here to make it effective: aiohttp? OPen new sessoin for getting the users? https://python.plainenglish.io/send-http-requests-as-fast-as-possible-in-python-304134d46604 +def list_course_users(session: requests.Session(), course_id: str) -> Any: + all_course_memberships = get_all_course_users_memberships(session, course_id) + users = [] + get_user_base_url = url_builder.base_v1().add_users().create() + for course_membership in all_course_memberships: + url = get_user_base_url + f'/{course_membership["userId"]}' + user = session.get(url, params={'fields' : 'id, userName, name, systemRoleIds'}) + user = json.loads(user.text) + users.append(user) + return users """ @@ -74,6 +85,24 @@ def sort_terms(terms): terms.remove(term) terms.sort(key=take_start_date) +def get_course_users_memberships(session: requests.Session, course_id: str, offset: int = None): + url = url_builder.base_v1().add_courses().add_id(course_id).add_users().create() + if offset: + url += f'?offset={offset}' + response = session.get(url) + response = json.loads(response.text) + return response + +def get_all_course_users_memberships(session: requests.Session, course_id: str): + course_memberships = [] + offset = 0 + page_results = 200 + while page_results == 200: + response = get_course_users_memberships(session, course_id, offset) + page_results = len(response['results']) + course_memberships += response['results'] + offset += 200 + return course_memberships def get_course_memberships(session: requests.Session, user_name: str): url = url_builder.base_v1().add_users().add_id( diff --git a/bbcli/views/course_view.py b/bbcli/views/course_view.py index 2320342b5c914adba28da36c3b9f6f99d7270c9d..3e061376f0c41b841d1dc7695f3e7ecba7c564fc 100644 --- a/bbcli/views/course_view.py +++ b/bbcli/views/course_view.py @@ -1,4 +1,8 @@ +import json import click +import csv +import time +from pathlib import Path def print_courses(courses): click.echo('\n{:<12} {:<5}\n'.format('Id', 'Course Name')) @@ -16,4 +20,27 @@ def print_course(course): click.echo('\n{:<12} {:<12}'.format('Id:', primary_id)) click.echo('{:<12} {:<12}'.format('Course Id:', course_id)) - click.echo('{:<12} {:<12}\n'.format('Name:', name)) \ No newline at end of file + click.echo('{:<12} {:<12}\n'.format('Name:', name)) + +def print_course_users(users, print_json=False): + if print_json: + click.echo(f'\n{json.dumps(users, indent=2)}\n\n') + else: + click.echo('\n{:<12} {:<5}\n'.format('Id', 'Username')) + for user in users: + user_id = user['id'] + name = user['userName'] + click.echo('{:<12} {:<5}'.format(user_id, name)) + click.echo('\n\n') + +def save_data_to_csv(users): + keys = users[0].keys() + + downloads_path = str(Path.home() / 'Downloads') + moment=time.strftime("%Y-%b-%d__%H_%M_%S",time.localtime()) + path = f'{downloads_path}/course_users_{moment}.csv' + with open(path, 'w', newline='') as output_file: + dict_writer = csv.DictWriter(output_file, keys) + dict_writer.writeheader() + dict_writer.writerows(users) + click.echo('\nSuccessfully exported users from the course to a csv in path: ' + path + '\n') \ No newline at end of file