Oceny załączników i przebieg zwrotny

To szósty z kolei przewodnik z serii o dodatkach do Classroom.

W tym samouczku zmodyfikujesz przykład z poprzedniego kroku, aby utworzyć załącznik typu aktywność z ocenianiem. Możesz też przekazać ocenę z powrotem do Google Classroom za pomocą programowania. Ocena pojawi się w dzienniku ocen nauczyciela jako wersja robocza.

Ten przewodnik różni się nieco od innych w serii, ponieważ przedstawia 2 możliwe podejścia do przekazywania ocen do Google Classroom. Oba mają odmienny wpływ na wrażenia dewelopera i użytkownika. Pamiętaj o obu tych aspektach podczas projektowania dodatku do Google Classroom. Więcej informacji o opcjach implementacji znajdziesz w przewodniku Interakcja z załącznikami.

Pamiętaj, że funkcje oceniania w interfejsie API są opcjonalne. Można ich używać w przypadku dowolnego załącznika typu „Aktywność”.

W tej instrukcji dowiesz się, jak:

  • Zmodyfikuj poprzednie żądania tworzenia załączników do interfejsu Classroom API, aby ustawić także licznik oceny załącznika.
  • Programowo ocenić przesłane przez ucznia treści i ustawić licznik oceny za załącznik.
  • Wprowadź 2 metody przekazywania oceny przesłanego projektu do Classroom za pomocą zalogowanych lub offline danych logowania nauczyciela.

Po zakończeniu zadania oceny pojawią się w dzienniku ocen w Classroom po uruchomieniu przekierowania. Dokładny moment zależy od podejścia do implementacji.

W tym przykładzie użyj ponownie aktywności z poprzedniego omówienia, w której uczniowi wyświetla się obraz słynnego zabytku i prosi o wpisanie jego nazwy. Przypisz pełną liczbę punktów za załącznik, jeśli uczeń wprowadzi prawidłową nazwę, a w przeciwnym razie zero.

Funkcja oceniania w interfejsie API dodatków do Classroom

Twój dodatek może ustawić zarówno licznik, jak i mianownik oceny za załącznik. Są one odpowiednio ustawiane za pomocą wartości pointsEarnedmaxPoints w interfejsie API. Gdy wartość maxPoints zostanie ustawiona, karta załącznika w interfejsie Classroom będzie ją wyświetlać.

Przykład wielu załączników z maksymalną liczbą punktów w jednym przydziale

Rysunek 1. Interfejs tworzenia projektu z 3 kartami załączników dodatku z ustawioną wartością maxPoints.

Interfejs Classroom Add-ons API umożliwia konfigurowanie ustawień i określanie punktów przyznawanych za załączniki. Nie są to oceny projektu. Ustawienia oceny projektu są jednak zgodne z ustawieniami oceny załącznika, który ma etykietę Synchronizacja ocen na karcie załącznika. Gdy załącznik „Synchronizacja ocen” ustawia wartość pointsEarned dla przesłanego przez ucznia projektu, ustawia też jego wersję roboczą oceny za projekt.

Zwykle pierwszy załącznik dodany do projektu, który określamaxPoints, otrzymuje etykietę „Synchronizacja ocen”. Aby zobaczyć etykietę „Synchronizacja oceny”, zobacz interfejs użytkownika tworzenia projektu na rysunku 1. Zwróć uwagę, że karta „Załącznik 1” ma etykietę „Synchronizacja oceny”, a ocena projektu w czerwonym polu została zaktualizowana do 50 punktów. Zwróć też uwagę, że chociaż rysunek 1 przedstawia 3 karty załączników, tylko jedna z nich ma etykietę „Synchronizacja ocen”. Jest to kluczowe ograniczenie obecnego rozwiązania: tylko jeden załącznik może mieć etykietę „Synchronizacja ocen”.

Jeśli masz wiele załączników z ustawieniem maxPoints, usunięcie załącznika z ustawieniem „Synchronizacja ocen” nie spowoduje włączenia opcji „Synchronizacja ocen” w przypadku pozostałych załączników. Dodanie kolejnego załącznika, który ustawia wartość maxPoints, powoduje włączenie synchronizacji ocen w przypadku nowego załącznika, a maksymalna ocena projektu zostanie dostosowana. Nie ma mechanizmu, który pozwalałby sprawdzić programowo, które załączniki mają etykietę „Synchronizacja ocen”, ani ile załączników ma konkretne zadanie.

Ustawianie maksymalnej oceny załącznika

W tej sekcji opisano ustawienie licznika dla oceny za załącznik, czyli maksymalnej możliwej liczby punktów, jaką wszyscy uczniowie mogą uzyskać za swoje przesłane treści. Aby to zrobić, ustaw wartość atrybutu maxPoints.

Aby włączyć funkcje oceniania, wystarczy wprowadzić tylko drobną modyfikację naszej dotychczasowej implementacji. Podczas tworzenia załącznika dodaj wartość maxPoints do tego samego obiektu AddOnAttachment, który zawiera pola studentWorkReviewUri i teacherViewUri, a także inne pola załącznika.

Pamiętaj, że domyślna maksymalna wartość dla nowego przypisania to 100. Zalecamy ustawienie wartości parametru maxPoints na inną niż 100, aby sprawdzić, czy oceny są ustawiane prawidłowo. W celu przeprowadzenia demonstracji ustaw wartość maxPoints na 50:

Python

Dodaj pole maxPoints podczas tworzenia obiektu attachment, tuż przed wysłaniem żądania CREATE do punktu końcowego courses.courseWork.addOnAttachments. Możesz to sprawdzić w pliku webapp/attachment_routes.py, jeśli zastosujesz się do naszego przykładu.

attachment = {
    # Specifies the route for a teacher user.
    "teacherViewUri": {
        "uri":
            flask.url_for(
                "load_activity_attachment",
                _scheme='https',
                _external=True),
    },
    # Specifies the route for a student user.
    "studentViewUri": {
        "uri":
            flask.url_for(
                "load_activity_attachment",
                _scheme='https',
                _external=True)
    },
    # Specifies the route for a teacher user when the attachment is
    # loaded in the Classroom grading view.
    "studentWorkReviewUri": {
        "uri":
            flask.url_for(
                "view_submission", _scheme='https', _external=True)
    },
    # Sets the maximum points that a student can earn for this activity.
    # This is the denominator in a fractional representation of a grade.
    "maxPoints": 50,
    # The title of the attachment.
    "title": f"Attachment {attachment_count}",
}

Na potrzeby tego demonstracyjnego przykładu wartość maxPoints jest również przechowywana w lokalnej bazie danych załączników. Dzięki temu nie trzeba będzie wykonywać dodatkowego wywołania interfejsu API podczas oceniania prac uczniów. Pamiętaj jednak, że nauczyciele mogą zmieniać ustawienia oceny projektu niezależnie od dodatku. Aby wyświetlić wartość maxPoints na poziomie projektu, prześlij żądanie GET do punktu końcowego courses.courseWork. W tym celu w polu CourseWork.id prześlij wartość itemId.

Teraz zaktualizuj model bazy danych, aby przechowywał on także wartość maxPoints załącznika. Zalecamy użycie wartości maxPoints z odpowiedzi CREATE:

Python

Najpierw dodaj do tabeli Attachment pole max_points. Jeśli postępujesz zgodnie z naszym przykładem, znajdziesz go w pliku webapp/models.py.

# Database model to represent an attachment.
class Attachment(db.Model):
    # The attachmentId is the unique identifier for the attachment.
    attachment_id = db.Column(db.String(120), primary_key=True)

    # The image filename to store.
    image_filename = db.Column(db.String(120))

    # The image caption to store.
    image_caption = db.Column(db.String(120))

    # The maximum number of points for this activity.
    max_points = db.Column(db.Integer)

Wróć do courses.courseWork.addOnAttachments CREATE. Zapisz wartość maxPoints zwróconą w odpowiedzi.

new_attachment = Attachment(
    # The new attachment's unique ID, returned in the CREATE response.
    attachment_id=resp.get("id"),
    image_filename=key,
    image_caption=value,
    # Store the maxPoints value returned in the response.
    max_points=int(resp.get("maxPoints")))
db.session.add(new_attachment)
db.session.commit()

Załącznik ma teraz maksymalną ocenę. Możesz teraz przetestować to działanie. Dodaj załącznik do nowego projektu i zobacz, że karta załącznika wyświetla etykietę „Synchronizacja oceny”, a wartość „Punkty” projektu ulega zmianie.

Ustawianie oceny za projekt ucznia w Classroom

W tej sekcji opisano ustawienie licznika dla oceny załącznika, czyli wyniku uzyskanego przez konkretnego ucznia za ten załącznik. Aby to zrobić, ustaw wartość pointsEarned dla przesyłanego przez ucznia projektu.

Musisz teraz podjąć ważną decyzję: jak Twój dodatek powinien wysłać prośbę o ustawienie pointsEarned?

Problem polega na tym, że ustawienie pointsEarned wymaga zakresu OAuth teacher. Nie przyznawaj uczniom uprawnień teacher, ponieważ może to spowodować nieoczekiwane działanie dodatku podczas interakcji uczniów z dodatkiem, np. wczytywanie ramki obrazu osadzonego Widok nauczyciela zamiast ramki obrazu osadzonego Widok ucznia. W związku z tym masz do wyboru 2 sposoby ustawienia wartości pointsEarned:

  • za pomocą danych logowania zalogowanego nauczyciela.
  • za pomocą zapisanych (offline) danych logowania nauczyciela;

W następnych sekcjach omawiamy kompromisy związane z każdym podejściem, a potem opisujemy ich implementację. Pamiętaj, że nasze przykłady pokazują oba podejścia do przekazywania oceny do Classroom. Aby dowiedzieć się, jak wybrać podejście podczas uruchamiania podanych przykładów, zapoznaj się z instrukcjami dotyczącymi danego języka:

Python

Znajdź deklarację SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS u góry pliku webapp/attachment_routes.py. Aby przekazać oceny za pomocą danych logowania nauczyciela, ustaw tę wartość na True. Ustaw tę wartość na False, aby przekazać oceny za pomocą zapisanych danych logowania, gdy uczeń prześle aktywność.

ustawiać oceny za pomocą danych logowania nauczyciela,

Aby wysłać żądanie ustawienia pointsEarned, użyj danych logowania zalogowanego użytkownika. Powinna być dość intuicyjna, ponieważ odzwierciedla pozostałe do tej pory wdrożone rozwiązania i nie wymaga wiele wysiłku.

Pamiętaj jednak, że nauczyciel tylko może wchodzić w interakcje z pracą ucznia w ramce iframe Sprawdzanie prac uczniów. Ma to kilka ważnych konsekwencji:

  • Oceny nie są wypełniane w Classroom, dopóki nauczyciel nie podejmie działań w interfejsie Classroom.
  • Aby wypełnić wszystkie oceny uczniów, nauczyciel może musieć otworzyć wszystkie przesłane przez nich treści.
  • Po otrzymaniu oceny przez Classroom może minąć chwila, zanim pojawi się ona w interfejsie Classroom. Opóźnienie wynosi zazwyczaj 5–10 sekund, ale może sięgać nawet 30 sekund.

Ze względu na te czynniki nauczyciele mogą musieć wykonać znaczną ilość czasochłonnej pracy ręcznej, aby w pełni wypełnić dziennik ocen.

Aby wdrożyć to podejście, dodaj jedno dodatkowe wywołanie interfejsu API do istniejącej ścieżki sprawdzenia pracy ucznia.

Po pobraniu rekordów przesłanych przez ucznia zadań i załączników oceń zadanie ucznia i zapisz wynikową ocenę. Ustaw ocenę w polu pointsEarned obiektu AddOnAttachmentStudentSubmission. Na koniec prześlij żądanie PATCH do punktu końcowego courses.courseWork.addOnAttachments.studentSubmissions z wystąpieniem AddOnAttachmentStudentSubmission w treści żądania. Pamiętaj, że w prośbie PATCH musisz też podać wartość pointsEarned w elementach updateMask:

Python

# Look up the student's submission in our database.
student_submission = Submission.query.get(flask.session["submissionId"])

# Look up the attachment in the database.
attachment = Attachment.query.get(student_submission.attachment_id)

grade = 0

# See if the student response matches the stored name.
if student_submission.student_response.lower(
) == attachment.image_caption.lower():
    grade = attachment.max_points

# Create an instance of the Classroom service.
classroom_service = ch._credential_handler.get_classroom_service()

# Build an AddOnAttachmentStudentSubmission instance.
add_on_attachment_student_submission = {
    # Specifies the student's score for this attachment.
    "pointsEarned": grade,
}

# Issue a PATCH request to set the grade numerator for this attachment.
patch_grade_response = classroom_service.courses().courseWork(
).addOnAttachments().studentSubmissions().patch(
    courseId=flask.session["courseId"],
    itemId=flask.session["itemId"],
    attachmentId=flask.session["attachmentId"],
    submissionId=flask.session["submissionId"],
    # updateMask is a list of fields being modified.
    updateMask="pointsEarned",
    body=add_on_attachment_student_submission).execute()

Ustawianie ocen za pomocą danych logowania nauczyciela offline

Drugie podejście do ustawiania ocen wymaga użycia zapisanych danych logowania nauczyciela, który utworzył załącznik. W ramach tej implementacji musisz utworzyć dane logowania za pomocą tokenów odświeżania i dostępu nauczyciela, który został wcześniej autoryzowany, a następnie użyć tych danych do ustawienia pointsEarned.

Kluczową zaletą tego podejścia jest to, że oceny są wypełniane bez konieczności podejmowania przez nauczyciela działań w interfejsie Classroom, co pozwala uniknąć problemów wymienionych powyżej. W efekcie użytkownicy końcowi postrzegają proces oceniania jako płynny i wydajny. Pozwala ono też wybrać moment, w którym przekazujesz oceny, na przykład gdy uczniowie ukończą aktywność lub asynchronicznie.

Aby wdrożyć to rozwiązanie, wykonaj te czynności:

  1. Modyfikowanie rekordów bazy danych użytkownika w celu przechowywania tokena dostępu.
  2. Zmień rekordy bazy danych załączników, aby przechowywać identyfikator nauczyciela.
  3. Pobierz dane logowania nauczyciela i (opcjonalnie) utwórz nową instancję usługi Classroom.
  4. Ustaw ocenę przesłanego projektu.

W ramach tej prezentacji ustaw ocenę, gdy uczeń ukończy zadanie, czyli prześle formularz w widoku ucznia.

Modyfikowanie rekordów bazy danych użytkownika w celu przechowywania tokena dostępu

Aby wykonywać wywołania interfejsu API, musisz mieć 2 unikalne tokeny: token odświeżaniatoken dostępu. Jeśli do tej pory postępujesz zgodnie z instrukcjami, w schemacie tabeli User powinien być już zapisany token odświeżania. Przechowywanie tokenu odświeżania wystarczy, gdy wywołujesz interfejs API tylko z zalogowanym użytkownikiem, ponieważ w ramach procesu uwierzytelniania otrzymujesz token dostępu.

Teraz jednak musisz wykonywać połączenia jako inny użytkownik niż zalogowany, co oznacza, że proces uwierzytelniania nie jest dostępny. Dlatego musisz przechowywać token dostępu wraz z tokenem odświeżania. Zaktualizuj schemat tabeli User, aby uwzględnić token dostępu:

Python

W naszym przykładzie znajduje się on w pliku webapp/models.py.

# Database model to represent a user.
class User(db.Model):
    # The user's identifying information:
    id = db.Column(db.String(120), primary_key=True)
    display_name = db.Column(db.String(80))
    email = db.Column(db.String(120), unique=True)
    portrait_url = db.Column(db.Text())

    # The user's refresh token, which will be used to obtain an access token.
    # Note that refresh tokens will become invalid if:
    # - The refresh token has not been used for six months.
    # - The user revokes your app's access permissions.
    # - The user changes passwords.
    # - The user belongs to a Google Cloud organization
    #   that has session control policies in effect.
    refresh_token = db.Column(db.Text())

    # An access token for this user.
    access_token = db.Column(db.Text())

Następnie zaktualizuj kod, który tworzy lub aktualizuje rekord User, aby przechowywał też token dostępu:

Python

W naszym przykładzie znajduje się on w pliku webapp/credential_handler.py.

def save_credentials_to_storage(self, credentials):
    # Issue a request for the user's profile details.
    user_info_service = googleapiclient.discovery.build(
        serviceName="oauth2", version="v2", credentials=credentials)
    user_info = user_info_service.userinfo().get().execute()
    flask.session["username"] = user_info.get("name")
    flask.session["login_hint"] = user_info.get("id")

    # See if we have any stored credentials for this user. If they have used
    # the add-on before, we should have received login_hint in the query
    # parameters.
    existing_user = self.get_credentials_from_storage(user_info.get("id"))

    # If we do have stored credentials, update the database.
    if existing_user:
        if user_info:
            existing_user.id = user_info.get("id")
            existing_user.display_name = user_info.get("name")
            existing_user.email = user_info.get("email")
            existing_user.portrait_url = user_info.get("picture")

        if credentials and credentials.refresh_token is not None:
            existing_user.refresh_token = credentials.refresh_token
            # Update the access token.
            existing_user.access_token = credentials.token

    # If not, this must be a new user, so add a new entry to the database.
    else:
        new_user = User(
            id=user_info.get("id"),
            display_name=user_info.get("name"),
            email=user_info.get("email"),
            portrait_url=user_info.get("picture"),
            refresh_token=credentials.refresh_token,
            # Store the access token as well.
            access_token=credentials.token)

        db.session.add(new_user)

    db.session.commit()

Modyfikowanie rekordów bazy danych załączników w celu przechowywania identyfikatora nauczyciela

Aby ustawić ocenę za aktywność, wywołaj funkcję ustawiania pointsEarned jako nauczyciela w kursie. Możesz to zrobić na kilka sposobów:

  • Przechowuj lokalne mapowanie danych uwierzytelniających nauczyciela do identyfikatorów kursów. Pamiętaj jednak, że ten sam nauczyciel nie zawsze jest powiązany z danym kursem.
  • Wyślij GET żądania do punktu końcowego interfejsu Classroom API courses, aby pobrać obecnych nauczycieli. Następnie przeprowadź zapytanie o lokalne rekordy użytkowników, aby znaleźć pasujące dane logowania nauczyciela.
  • Podczas tworzenia załącznika dodatku zapisz identyfikator nauczyciela w lokalnej bazie danych załączników. Następnie pobierz dane logowania nauczyciela z parametru attachmentId przekazanego do ramki iframe widoku ucznia.

W tym przykładzie pokazujemy ostatnią opcję, ponieważ oceny są przyznawane, gdy uczeń ukończy załącznik do aktywności.

Dodaj pole identyfikatora nauczyciela do tabeli Attachment w bazie danych:

Python

W naszym przykładzie znajduje się on w pliku webapp/models.py.

# Database model to represent an attachment.
class Attachment(db.Model):
    # The attachmentId is the unique identifier for the attachment.
    attachment_id = db.Column(db.String(120), primary_key=True)

    # The image filename to store.
    image_filename = db.Column(db.String(120))

    # The image caption to store.
    image_caption = db.Column(db.String(120))

    # The maximum number of points for this activity.
    max_points = db.Column(db.Integer)

    # The ID of the teacher that created the attachment.
    teacher_id = db.Column(db.String(120))

Następnie zaktualizuj kod, który tworzy lub aktualizuje rekord Attachment, aby przechowywał też identyfikator twórcy:

Python

W naszym przykładzie znajduje się ona w metodzie create_attachments w pliku webapp/attachment_routes.py.

# Store the attachment by id.
new_attachment = Attachment(
    # The new attachment's unique ID, returned in the CREATE response.
    attachment_id=resp.get("id"),
    image_filename=key,
    image_caption=value,
    max_points=int(resp.get("maxPoints")),
    teacher_id=flask.session["login_hint"])
db.session.add(new_attachment)
db.session.commit()

Pobieranie danych logowania nauczyciela

Znajdź ścieżkę, która obsługuje element iframe widoku ucznia. Bezpośrednio po zapisaniu odpowiedzi ucznia w lokalnej bazie danych pobierz dane logowania nauczyciela z lokalnego magazynu. Powinien być to prosty proces, ponieważ wszystko zostało przygotowane w poprzednich 2 krokach. Możesz też użyć tych danych, aby utworzyć nową instancję usługi Classroom dla nauczyciela:

Python

W naszym przykładzie znajduje się ona w metodzie load_activity_attachment w pliku webapp/attachment_routes.py.

# Create an instance of the Classroom service using the tokens for the
# teacher that created the attachment.

# We're assuming that there are already credentials in the session, which
# should be true given that we are adding this within the Student View
# route; we must have had valid credentials for the student to reach this
# point. The student credentials will be valid to construct a Classroom
# service for another user except for the tokens.
if not flask.session.get("credentials"):
    raise ValueError(
        "No credentials found in session for the requested user.")

# Make a copy of the student credentials so we don't modify the original.
teacher_credentials_dict = deepcopy(flask.session.get("credentials"))

# Retrieve the requested user's stored record.
teacher_record = User.query.get(attachment.teacher_id)

# Apply the user's tokens to the copied credentials.
teacher_credentials_dict["refresh_token"] = teacher_record.refresh_token
teacher_credentials_dict["token"] = teacher_record.access_token

# Construct a temporary credentials object.
teacher_credentials = google.oauth2.credentials.Credentials(
    **teacher_credentials_dict)

# Refresh the credentials if necessary; we don't know when this teacher last
# made a call.
if teacher_credentials.expired:
    teacher_credentials.refresh(Request())

# Request the Classroom service for the specified user.
teacher_classroom_service = googleapiclient.discovery.build(
    serviceName=CLASSROOM_API_SERVICE_NAME,
    version=CLASSROOM_API_VERSION,
    credentials=teacher_credentials)

Ustawianie oceny przesłanego projektu

Dalsza procedura jest identyczna jak w przypadku używania danych logowania nauczyciela. Pamiętaj jednak, że połączenie należy wykonać za pomocą danych logowania nauczyciela pobranych w poprzednim kroku:

Python

# Issue a PATCH request as the teacher to set the grade numerator for this
# attachment.
patch_grade_response = teacher_classroom_service.courses().courseWork(
).addOnAttachments().studentSubmissions().patch(
    courseId=flask.session["courseId"],
    itemId=flask.session["itemId"],
    attachmentId=flask.session["attachmentId"],
    submissionId=flask.session["submissionId"],
    # updateMask is a list of fields being modified.
    updateMask="pointsEarned",
    body=add_on_attachment_student_submission).execute()

Testowanie dodatku

Podobnie jak w poprzednim samouczku, utwórz projekt z załącznikiem typu aktywność jako nauczyciel, prześlij odpowiedź jako uczeń, a następnie otwórz przesłany projekt w ramce przeglądania prac uczniów. Ocena powinna być widoczna w różnych momentach w zależności od podejścia do implementacji:

  • Jeśli po zakończeniu przez ucznia wykonywania zadania zdecydujesz się przekazać mu ocenę, przed otwarciem ramki ładowania Oceny pracy ucznia powinna już być widoczna jego ocena robocza w interfejsie. Możesz też zobaczyć je na liście uczniów podczas otwierania projektu oraz w polu „Ocena” obok ramki przeglądania zadań uczniów.
  • Jeśli nauczyciel otworzy iframe Zadania uczniów do sprawdzenia, a Ty wybierzesz opcję przekazania oceny, ocena powinna pojawić się w polu „Ocena” zaraz po załadowaniu iframe. Jak wspomniano powyżej, może to potrwać do 30 sekund. Następnie ocena dla konkretnego ucznia powinna pojawić się również w innych widokach dziennika ocen w Classroom.

Sprawdź, czy dla ucznia wyświetla się prawidłowa ocena.

Gratulacje! Możesz przejść do następnego kroku: tworzenia załączników poza Google Classroom.