API and Python Script to list Quizzes that have Shuffle turned on and "All of the above" etc answers

Jump to solution
RupertRussell
Community Contributor

My use case is to search a list courses that contain Classic Quizzes and to list any Quizzes that have "shuffle_answers":true and that contain one or more questions that have "All of the above" as a answer or distractor.

Unfortunately we have thousands of quizzes and most probably 1-2 % would fall into this category. 😞

Being able to filter out a list of all quizzes with "shuffle_answers":true  would be a start but I can't work out a smart way to then search the questions in the quizzes for "All of the above" or "Both A & D" or "None of the above" type distractors.

I am hoping that I don't have to write a AHK script to do this.
I would like a Python Script to do this if at all possible.

The answer to this question may hold some clues.

https://community.canvaslms.com/t5/Canvas-Developers-Group/Listing-all-question-groups-in-a-quiz/m-p...

 

Labels (3)
0 Likes
1 Solution
RupertRussell
Community Contributor
Author

 

Working with Chat GPT I have a working python script as follows:

# Fetches classic quizzes and associated questions from a list of Canvas courses.
# Not tested with new quizzes 
# Retrieves quiz details including: Course Name	Quiz Name, Number of Questions,	Shuffle Answers, Published,	Contains Search Strings
# Generates a CSV report
# Code Generated by Chat GPT 14/04/2024
# With prompts by Rupert Russell 

import os
import requests
import csv
import json

# Define your Canvas API URL and access token
API_URL = "https://....instructure.com/api/v1/"
ACCESS_TOKEN = '...' # generate your own token 


# Function to fetch quizzes for a course from Canvas
def get_quizzes(course_id):
    url = f"{API_URL}/courses/{course_id}/quizzes"
    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    quizzes = []
    page = 1
    while True:
        params = {"page": page}
        try:
            response = requests.get(url, headers=headers, params=params)
            response.raise_for_status()  # Raise an exception for 4xx/5xx status codes
            quizzes_page = json.loads(response.text)  # Convert response text to dictionary using json.loads()
            quizzes.extend(quizzes_page)
            if "next" not in response.links:
                break  # No more pages to fetch
            page += 1
        except requests.exceptions.RequestException as e:
            print(f"Error fetching quizzes for course {course_id}: {e}")
            return []  # Return an empty list if there's an error
    return quizzes

# Function to fetch questions for a quiz from Canvas
def get_quiz_questions(course_id, quiz_id):
    url = f"{API_URL}/courses/{course_id}/quizzes/{quiz_id}/questions"
    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()  # Raise an exception for 4xx/5xx status codes
        return json.loads(response.text)  # Convert response text to dictionary using json.loads()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching questions for quiz {quiz_id} in course {course_id}: {e}")
        return []  # Return an empty list if there's an error

# Function to get quiz details from Canvas
def get_quiz_details(course_id, quiz_id):
    url = f"{API_URL}/courses/{course_id}/quizzes/{quiz_id}"
    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()  # Raise an exception for 4xx/5xx status codes
        return json.loads(response.text)  # Convert response text to dictionary using json.loads()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching details for quiz {quiz_id} in course {course_id}: {e}")
        return {}  # Return an empty dictionary if there's an error

# Function to fetch question bank details from Canvas
def get_question_bank_details(course_id, question_bank_id):
    url = f"{API_URL}/courses/{course_id}/question_groups/{question_bank_id}"
    headers = {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()  # Raise an exception for 4xx/5xx status codes
        return json.loads(response.text)  # Convert response text to dictionary using json.loads()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching details for question bank {question_bank_id} in course {course_id}: {e}")
        return {}  # Return an empty dictionary if there's an error

# Main function to fetch courses with quizzes and save as CSV
def main(course_numbers, search_strings):
    total_courses = len(course_numbers)
    csv_version = 1
    while True:
        csv_filename = f'courses_with_quizzes_v{csv_version:03d}.csv'
        if not os.path.exists(csv_filename):
            break
        csv_version += 1

    with open(csv_filename, 'w', newline='') as csvfile:
        fieldnames = ['Course Name', 'Quiz Name', 'Number of Questions', 'Shuffle Answers', 'Published', 'Contains Search Strings']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        
        for i, course_number in enumerate(course_numbers, start=1):
            print(f"Checking course {i}/{total_courses}")
            quizzes = get_quizzes(course_number)
            for quiz in quizzes:
                shuffle_answers = 'True' if quiz.get('shuffle_answers', False) else 'False'
                quiz_questions = get_quiz_questions(course_number, quiz['id'])
                num_questions = len(quiz_questions)
                contains_search_strings = any(
                    any(search_string.lower() in choice['text'].lower() for choice in question.get('answers', []))
                    for question in quiz_questions
                    for search_string in search_strings
                )

                # Check questions in associated question banks
                question_bank_ids = [group['id'] for group in quiz.get('question_groups', [])]
                for question_bank_id in question_bank_ids:
                    question_bank_details = get_question_bank_details(course_number, question_bank_id)
                    if not question_bank_details:
                        continue  # Skip if unable to fetch question bank details
                    questions_in_bank = question_bank_details.get('questions', [])
                    contains_search_strings |= any(
                        any(search_string.lower() in choice['text'].lower() for choice in question.get('answers', []))
                        for question in questions_in_bank
                        for search_string in search_strings
                    )

                quiz_details = get_quiz_details(course_number, quiz['id'])
                is_published = quiz_details.get('published', False)
                writer.writerow({'Course Name': course_number, 'Quiz Name': quiz['title'], 'Number of Questions': num_questions, 'Shuffle Answers': shuffle_answers, 'Published': is_published, 'Contains Search Strings': contains_search_strings})

    print(f"CSV file '{csv_filename}' has been created.")

if __name__ == "__main__":
    # Example usage:
    courses = [522, 20451]
    search_strings = ["All of the above", "None of the above", "Some other string"]
    main(courses, search_strings)

 

 

 

View solution in original post