Wprowadzenie do ocen cząstkowych

rubric to szablon, którego nauczyciele mogą używać podczas oceniania prac uczniów. Interfejs Classroom API umożliwia zarządzanie tymi rubrykami w imieniu nauczyciela, a także odczytywanie ocen w rubrykach w pracach uczniów.

Widok kryteriów oceny w interfejsie Classroom Rysunek 1. Widok przykładowych kryteriów oceny w projekcie w Classroom.

W tym przewodniku wyjaśniamy podstawowe pojęcia i funkcje interfejsu Rubrics API. Więcej informacji o ogólnej strukturze kryteriów oceny i sposobie oceniania w interfejsie Classroom znajdziesz w tych artykułach w Centrum pomocy.

Wymagania wstępne

W tym przewodniku przyjęto, że masz:

  • Pythona w wersji 3.8.6 lub nowszej,
  • Narzędzie do zarządzania pakietami pip
  • projekt Google Cloud,
  • konto edukacyjne Google Workspace for Education z włączoną usługą Google Classroom i przypisaną licencją Google Workspace for Education Plus. Jeśli nie masz konta demonstracyjnego dla programistów, możesz o nie poprosić o ulepszone.

  • Zajęcia testowe z co najmniej 1 kontem ucznia testowego. Jeśli nie masz zajęć w Classroom, których możesz użyć do testowania, utwórz je w interfejsie i dodaj ucznia testowego.

Autoryzowanie danych logowania aplikacji komputerowej

Aby uwierzytelnić się jako użytkownik i uzyskać dostęp do danych użytkownika w aplikacji, musisz utworzyć co najmniej 1 identyfikator klienta OAuth 2.0. Identyfikator klienta wskazuje konkretną aplikację na serwerach OAuth Google. Jeśli Twoja aplikacja działa na kilku platformach, musisz utworzyć osobny identyfikator klienta dla każdej z nich.

  1. W konsoli Google Cloud otwórz stronę Dane logowania Google Cloud.
  2. Kliknij Utwórz dane logowania > Identyfikator klienta OAuth.
  3. Kliknij Typ aplikacji > Aplikacja komputerowa.
  4. W polu Nazwa wpisz nazwę danych logowania. Ta nazwa jest widoczna tylko w konsoli Google Cloud. Na przykład „Klient rubryk”.
  5. Kliknij Utwórz. Pojawi się ekran Utworzono klienta OAuth z nowym identyfikatorem klienta i tajnym kluczem klienta.
  6. Kliknij Pobierz JSON, a potem OK. Nowo utworzone dane logowania pojawią się w sekcji Identyfikatory klientów OAuth 2.0.
  7. Zapisz pobrany plik JSON jako credentials.json i przenieś go do katalogu roboczego.
  8. Kliknij Utwórz dane logowania > Klucz interfejsu API i zapisz klucz interfejsu API.

Więcej informacji znajdziesz w artykule Tworzenie danych logowania.

Konfigurowanie zakresów OAuth

W zależności od dotychczasowych zakresów OAuth w projekcie może być konieczne skonfigurowanie dodatkowych zakresów.

  1. Otwórz ekran zgody OAuth.
  2. Kliknij Edytuj aplikację > Zapisz i kontynuuj , aby przejść do ekranu Zakresy.
  3. Kliknij Dodaj lub usuń zakresy.
  4. Jeśli nie masz jeszcze tych zakresów, dodaj je:
    • https://www.googleapis.com/auth/classroom.coursework.students
    • https://www.googleapis.com/auth/classroom.courses
  5. Następnie kliknij Aktualizuj > Zapisz i kontynuuj > Zapisz i kontynuuj > Wróć do panelu.

Więcej informacji znajdziesz w artykule Konfigurowanie ekranu zgody OAuth.

Zakres classroom.coursework.students umożliwia odczyt i zapis rubryk (wraz z dostępem do CourseWork), a zakres classroom.courses umożliwia odczyt i zapis kursów.

Zakresy wymagane w przypadku danej metody są wymienione w dokumentacji metody. Jako przykład zobacz courses.courseWork.rubrics.create zakresy autoryzacji. Wszystkie zakresy Classroom znajdziesz w sekcji Zakresy protokołu OAuth 2.0 dla interfejsów API Google.

Konfigurowanie przykładu

W katalogu roboczym zainstaluj bibliotekę klienta Google dla Pythona:

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

Utwórz plik o nazwie main.py, który będzie tworzyć bibliotekę klienta i autoryzować użytkownika, używając klucza interfejsu API zamiast YOUR_API_KEY:

import json
import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/classroom.courses',
          'https://www.googleapis.com/auth/classroom.coursework.students']

def build_authenticated_service(api_key):
    """Builds the Classroom service."""
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run.
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:
        # Build the Classroom service.
        service = build(
            serviceName="classroom",
            version="v1",
            credentials=creds,
            discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=DEVELOPER_PREVIEW&key={api_key}")

        return service

    except HttpError as error:
        print('An error occurred: %s' % error)

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

Uruchom skrypt za pomocą polecenia python main.py. Powinien pojawić się monit o zalogowanie się i wyrażenie zgody na zakresy OAuth.

Tworzenie zadania

Kryteria oceny są powiązane z projektem lub CourseWork i mają znaczenie tylko w kontekście tego CourseWork. Kryteria oceny mogą być tworzone tylko przez projekt w chmurze Google, który utworzył element nadrzędny CourseWork item. Na potrzeby tego przewodnika utwórz nowy projekt CourseWork za pomocą skryptu.

Dodaj do pliku main.py te elementy:

def get_latest_course(service):
    """Retrieves the last created course."""
    try:
        response = service.courses().list(pageSize=1).execute()
        courses = response.get("courses", [])
        if not courses:
            print("No courses found. Did you remember to create one in the UI?")
            return
        course = courses[0]
        return course

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

def create_coursework(service, course_id):
    """Creates and returns a sample coursework."""
    try:
        coursework = {
            "title": "Romeo and Juliet analysis.",
            "description": """Write a paper arguing that Romeo and Juliet were
                                time travelers from the future.""",
            "workType": "ASSIGNMENT",
            "state": "PUBLISHED",
        }
        coursework = service.courses().courseWork().create(
            courseId=course_id, body=coursework).execute()
        return coursework

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

Teraz zaktualizuj plik main.py, aby pobrać course_id utworzonych zajęć testowych, utworzyć nowy przykładowy projekt i pobrać coursework_id projektu:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    course = get_latest_course(service)
    course_id = course.get("id")
    course_name = course.get("name")
    print(f"'{course_name}' course ID: {course_id}")

    coursework = create_coursework(service, course_id)
    coursework_id = coursework.get("id")
    print(f"Assignment created with ID {coursework_id}")

    #TODO(developer): Save the printed course and coursework IDs.

Zapisz course_id i coursework_id. Są one potrzebne do wszystkich operacji CRUD w rubrykach.

Powinien być teraz dostępny przykładowy projekt CourseWork w Classroom.

Widok projektu w interfejsie Classroom Rysunek 2. Widok przykładowego projektu w Classroom.

Sprawdzanie, czy użytkownik może korzystać z funkcji

Do tworzenia i aktualizowania rubryk wymagane jest, aby zarówno użytkownik wysyłający prośbę, jak i właściciel odpowiedniego kursu mieli przypisaną licencję Google Workspace for Education Plus. Classroom obsługuje punkt końcowy sprawdzania, czy użytkownik może korzystać z funkcji, aby umożliwić programistom określanie, do jakich funkcji użytkownik ma dostęp.

Zaktualizuj i uruchom plik main.py, aby potwierdzić, że Twoje konto testowe ma dostęp do funkcji rubryk:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    capability = service.userProfiles().checkUserCapability(
        userId='me',
        # Specify the preview version. checkUserCapability is
        # supported in V1_20240930_PREVIEW and later.
        previewVersion="V1_20240930_PREVIEW",
        capability="CREATE_RUBRIC").execute()

    if not capability.get('allowed'):
      print('User ineligible for rubrics creation.')
      # TODO(developer): in a production app, this signal could be used to
      # proactively hide any rubrics related features from users or encourage
      # them to upgrade to the appropriate license.
    else:
      print('User eligible for rubrics creation.')

Tworzenie kryteriów oceny

Możesz już zacząć zarządzać rubrykami.

Kryteria oceny można utworzyć w CourseWork za pomocą wywołania create() zawierającego pełny obiekt kryteriów oceny, w którym pominięto właściwości ID kryteriów i poziomów (są one generowane podczas tworzenia).

Dodaj do pliku main.py tę funkcję:

def create_rubric(service, course_id, coursework_id):
    """Creates an example rubric on a coursework."""
    try:
        body = {
            "criteria": [
                {
                    "title": "Argument",
                    "description": "How well structured your argument is.",
                    "levels": [
                        {"title": "Convincing",
                         "description": "A compelling case is made.", "points": 30},
                        {"title": "Passable",
                         "description": "Missing some evidence.", "points": 20},
                        {"title": "Needs Work",
                         "description": "Not enough strong evidence..", "points": 0},
                    ]
                },
                {
                    "title": "Spelling",
                    "description": "How well you spelled all the words.",
                    "levels": [
                        {"title": "Perfect",
                         "description": "No mistakes.", "points": 20},
                        {"title": "Great",
                         "description": "A mistake or two.", "points": 15},
                        {"title": "Needs Work",
                         "description": "Many mistakes.", "points": 5},
                    ]
                },
                {
                    "title": "Grammar",
                    "description": "How grammatically correct your sentences are.",
                    "levels": [
                        {"title": "Perfect",
                         "description": "No mistakes.", "points": 20},
                        {"title": "Great",
                         "description": "A mistake or two.", "points": 15},
                        {"title": "Needs Work",
                         "description": "Many mistakes.", "points": 5},
                    ]
                },
            ]
        }

        rubric = service.courses().courseWork().rubrics().create(
            courseId=course_id, courseWorkId=coursework_id, body=body
            ).execute()
        print(f"Rubric created with ID {rubric.get('id')}")
        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

Następnie zaktualizuj i uruchom plik main.py, aby utworzyć przykładowe kryteria oceny, używając identyfikatorów Course i CourseWork z wcześniejszych kroków:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    capability = service.userProfiles().checkUserCapability(
        userId='me',
        # Specify the preview version. checkUserCapability is
        # supported in V1_20240930_PREVIEW and later.
        previewVersion="V1_20240930_PREVIEW",
        capability="CREATE_RUBRIC").execute()

    if not capability.get('allowed'):
      print('User ineligible for rubrics creation.')
      # TODO(developer): in a production app, this signal could be used to
      # proactively hide any rubrics related features from users or encourage
      # them to upgrade to the appropriate license.
    else:
      rubric = create_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
      print(json.dumps(rubric, indent=4))

Kilka uwag na temat reprezentacji rubryki:

  • Kolejność kryteriów i poziomów jest odzwierciedlana w interfejsie Classroom.
  • Poziomy z punktami (te z właściwością points) muszą być posortowane według punktów w kolejności rosnącej lub malejącej (nie mogą być uporządkowane losowo).
  • Nauczyciele mogą zmieniać kolejność kryteriów i poziomów z punktami (ale nie poziomów bez punktów) w interfejsie, co zmienia ich kolejność w danych.

Więcej informacji o strukturze rubryk znajdziesz w sekcji Ograniczenia.

W interfejsie powinny być widoczne kryteria oceny w projekcie.

Widok kryteriów oceny w interfejsie Classroom Rysunek 3. Widok przykładowych kryteriów oceny w projekcie w Classroom.

Odczytywanie rubryki

Rubryki można odczytywać za pomocą standardowych list() i get() metod.

W projekcie może być co najwyżej 1 rubryka, więc metoda list() może wydawać się nieintuicyjna, ale jest przydatna, jeśli nie masz jeszcze identyfikatora rubryki. Jeśli z CourseWork nie są powiązane żadne kryteria oceny, odpowiedź list() jest pusta.

Dodaj do pliku main.py tę funkcję:

def get_rubric(service, course_id, coursework_id):
    """
    Get the rubric on a coursework. There can only be at most one.
    Returns null if there is no rubric.
    """
    try:
        response = service.courses().courseWork().rubrics().list(
            courseId=course_id, courseWorkId=coursework_id
            ).execute()

        rubrics = response.get("rubrics", [])
        if not rubrics:
            print("No rubric found for this assignment.")
            return
        rubric = rubrics[0]
        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

Zaktualizuj i uruchom main.py, aby pobrać dodane kryteria oceny:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    rubric = get_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
    print(json.dumps(rubric, indent=4))

    #TODO(developer): Save the printed rubric ID.

Zapisz właściwość id w rubryce na potrzeby kolejnych kroków.

Metoda Get() sprawdza się, gdy masz identyfikator kryteriów oceny. Użycie metody get() w funkcji może wyglądać tak:

def get_rubric(service, course_id, coursework_id, rubric_id):
    """
    Get the rubric on a coursework. There can only be at most one.
    Returns a 404 if there is no rubric.
    """
    try:
        rubric = service.courses().courseWork().rubrics().get(
            courseId=course_id,
            courseWorkId=coursework_id,
            id=rubric_id
        ).execute()

        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

Ta implementacja zwraca kod 404, jeśli nie ma rubryki.

Aktualizowanie kryteriów oceny

Aktualizacje kryteriów oceny są wykonywane za pomocą patch() wywołań. Ze względu na złożoną strukturę kryteriów oceny aktualizacje muszą być wykonywane za pomocą wzorca odczyt-modyfikacja-zapis, w którym zastępowana jest cała właściwość criteria.

Obowiązują te reguły aktualizacji:

  1. Kryteria lub poziomy dodane bez identyfikatora są traktowane jako dodatki.
  2. Kryteria lub poziomy brakujące w porównaniu z poprzednią wersją są traktowane jako usunięcia.
  3. Kryteria lub poziomy z istniejącym identyfikatorem, ale zmodyfikowanymi danymi są traktowane jako edycje. Niezmienione właściwości pozostają bez zmian.
  4. Kryteria lub poziomy podane z nowymi lub nieznanymi identyfikatorami są traktowane jako błędy.
  5. Kolejność nowych kryteriów i poziomów jest traktowana jako nowa kolejność w interfejsie (z uwzględnieniem wspomnianych ograniczeń).

Dodaj funkcję aktualizowania kryteriów oceny:

def update_rubric(service, course_id, coursework_id, rubric_id, body):
    """
    Updates the rubric on a coursework.
    """
    try:
        rubric = service.courses().courseWork().rubrics().patch(
            courseId=course_id,
            courseWorkId=coursework_id,
            id=rubric_id,
            body=body,
            updateMask='criteria'
        ).execute()

        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

W tym przykładzie pole criteria jest określone do modyfikacji za pomocą updateMask.

Następnie zmodyfikuj plik main.py, aby wprowadzić zmiany zgodnie z każdą z wymienionych reguł aktualizacji:

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    capability = service.userProfiles().checkUserCapability(
        userId='me',
        # Specify the preview version. checkUserCapability is
        # supported in V1_20240930_PREVIEW and later.
        previewVersion="V1_20240930_PREVIEW",
        capability="CREATE_RUBRIC").execute()

    if not capability.get('allowed'):
      print('User ineligible for rubrics creation.')
      # TODO(developer): in a production app, this signal could be used to
      # proactively hide any rubrics related features from users or encourage
      # them to upgrade to the appropriate license.
    else:
        # Get the latest rubric.
        rubric = get_rubric(service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
        criteria = rubric.get("criteria")
        """
        The "criteria" property should look like this:
        [
            {
                "id": "NkEyMdMyMzM2Nxkw",
                "title": "Argument",
                "description": "How well structured your argument is.",
                "levels": [
                    {
                        "id": "NkEyMdMyMzM2Nxkx",
                        "title": "Convincing",
                        "description": "A compelling case is made.",
                        "points": 30
                    },
                    {
                        "id": "NkEyMdMyMzM2Nxky",
                        "title": "Passable",
                        "description": "Missing some evidence.",
                        "points": 20
                    },
                    {
                        "id": "NkEyMdMyMzM2Nxkz",
                        "title": "Needs Work",
                        "description": "Not enough strong evidence..",
                        "points": 0
                    }
                ]
            },
            {
                "id": "NkEyMdMyMzM2Nxk0",
                "title": "Spelling",
                "description": "How well you spelled all the words.",
                "levels": [...]
            },
            {
                "id": "NkEyMdMyMzM2Nxk4",
                "title": "Grammar",
                "description": "How grammatically correct your sentences are.",
                "levels": [...]
            }
        ]
        """

        # Make edits. This example will make one of each type of change.

        # Add a new level to the first criteria. Levels must remain sorted by
        # points.
        new_level = {
            "title": "Profound",
            "description": "Truly unique insight.",
            "points": 50
        }
        criteria[0]["levels"].insert(0, new_level)

        # Remove the last criteria.
        del criteria[-1]

        # Update the criteria titles with numeric prefixes.
        for index, criterion in enumerate(criteria):
            criterion["title"] = f"{index}: {criterion['title']}"

        # Resort the levels from descending to ascending points.
        for criterion in criteria:
            criterion["levels"].sort(key=lambda level: level["points"])

        # Update the rubric with a patch call.
        new_rubric = update_rubric(
            service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID, YOUR_RUBRIC_ID, rubric)

        print(json.dumps(new_rubric, indent=4))

Zmiany powinny być teraz widoczne dla nauczyciela w Classroom.

Widok zaktualizowanych kryteriów oceny w interfejsie Classroom Rysunek 4. Widok zaktualizowanych kryteriów oceny.

Wyświetlanie prac ocenionych za pomocą rubryki

Obecnie nie można oceniać prac uczniów za pomocą rubryki za pomocą interfejsu API, ale można odczytywać oceny w rubrykach w pracach, które zostały ocenione za pomocą rubryki w interfejsie Classroom.

Jako uczeń w interfejsie Classroom wykonaj i oddaj przykładowy projekt. Następnie jako nauczyciel ręcznie oceń projekt za pomocą kryteriów oceny.

Widok oceny według kryteriów oceny w interfejsie Classroom Rysunek 5. Widok kryteriów oceny podczas oceniania z perspektywy nauczyciela.

StudentSubmissions , które zostały ocenione za pomocą kryteriów oceny, mają 2 nowe właściwości: draftRubricGrades i assignedRubricGrades, które reprezentują punkty i poziomy wybrane przez nauczyciela odpowiednio w stanach oceniania „wersja robocza” i „przypisane”.

Do wyświetlania ocenionych prac możesz używać dotychczasowych metod studentSubmissions.get() i studentSubmissions.list().

Dodaj do pliku main.py tę funkcję, aby wyświetlić listę prac uczniów:

def get_latest_submission(service, course_id, coursework_id):
    """Retrieves the last submission for an assignment."""
    try:
        response = service.courses().courseWork().studentSubmissions().list(
            courseId = course_id,
            courseWorkId = coursework_id,
            pageSize=1
        ).execute()
        submissions = response.get("studentSubmissions", [])
        if not submissions:
            print(
                """No submissions found. Did you remember to turn in and grade
                   the assignment in the UI?""")
            return
        submission = submissions[0]
        return submission

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

Następnie zaktualizuj i uruchom plik main.py, aby wyświetlić oceny prac.

if __name__ == '__main__':
    service = build_authenticated_service(YOUR_API_KEY)

    submission = get_latest_submission(
        service, YOUR_COURSE_ID, YOUR_COURSEWORK_ID)
    print(json.dumps(submission, indent=4))

Pola draftRubricGrades i assignedRubricGrades zawierają:

  • criterionId odpowiednich kryteriów oceny.
  • points przypisane przez nauczyciela do każdego kryterium. Mogą one pochodzić z wybranego poziomu, ale nauczyciel mógł je też zastąpić.
  • levelId poziomu wybranego dla każdego kryterium. Jeśli nauczyciel nie wybrał poziomu, ale przypisał punkty do kryterium, to pole nie jest obecne.

Te listy zawierają tylko wpisy dotyczące kryteriów, w przypadku których nauczyciel wybrał poziom lub ustawił punkty. Jeśli na przykład nauczyciel podczas oceniania zdecyduje się na interakcję tylko z 1 kryterium, pola draftRubricGrades i assignedRubricGrades będą zawierać tylko 1 element, nawet jeśli kryteria oceny mają wiele kryteriów.

Usuwanie rubryki

Rubrykę można usunąć za pomocą standardowego delete() żądania. Poniższy kod przedstawia przykładową funkcję, ale ponieważ ocenianie już się rozpoczęło, nie możesz usunąć bieżącej rubryki:

def delete_rubric(service, course_id, coursework_id, rubric_id):
    """Deletes the rubric on a coursework."""
    try:
        service.courses().courseWork().rubrics().delete(
            courseId=course_id,
            courseWorkId=coursework_id,
            id=rubric_id
        ).execute()

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error

Eksportowanie i importowanie rubryk

Rubryki można ręcznie eksportować do Arkuszy Google, aby nauczyciele mogli ich ponownie używać.

Oprócz określania kryteriów oceny w kodzie można też tworzyć i aktualizować kryteria oceny na podstawie tych wyeksportowanych arkuszy, określając sourceSpreadsheetId w treści kryteriów oceny zamiast criteria:

def create_rubric_from_sheet(service, course_id, coursework_id, sheet_id):
    """Creates an example rubric on a coursework."""
    try:
        body = {
            "sourceSpreadsheetId": sheet_id
        }

        rubric = service.courses().courseWork().rubrics().create(
            courseId=course_id, courseWorkId=coursework_id, body=body
            ).execute()

        print(f"Rubric created with ID {rubric.get('id')}")
        return rubric

    except HttpError as error:
        print(f"An error occurred: {error}")
        return error