Oceny załączników i przebieg zwrotny

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

W tej instrukcji 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ż zawiera 2 możliwe podejścia do przekazywania ocen do Google Classroom. Oba te czynniki mają znaczenie dla wrażeń zarówno dewelopera, jak i użytkownika. Pamiętaj o nich podczas projektowania dodatku do Google Classroom. Więcej informacji o opcjach implementacji znajdziesz w przewodniku Interakcje 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:

  • Zmień poprzednie żądania tworzenia załączników do interfejsu Classroom API, aby ustawić także licznik oceny załącznika.
  • Programowo ocenić projekt ucznia i ustawić licznik oceny załącznika.
  • Wdrożyć 2 metody przekazywania oceny przesłania 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 aktywności z poprzedniego omówienia, w której uczeń widzi obraz słynnego zabytku i jest proszony o wpisanie jego nazwy. Przypisz pełną liczbę punktów za załącznik, jeśli uczeń wpisze prawidłową nazwę, a w przeciwnym razie zero.

Funkcja oceniania za pomocą interfejsu 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. Po ustawieniu wartości maxPoints karta załącznika w interfejsie Classroom wyświetla tę wartość.

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 projektu ucznia, ustawia też wersję roboczą oceny projektu ucznia.

Zwykle pierwszy załącznik dodany do projektu, który określa maxPoints, otrzymuje etykietę „Synchronizacja oceny”. Aby zobaczyć etykietę „Synchronizacja ocen”, 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 z załącznikami, tylko jedna z nich ma etykietę „Synchronizacja ocen”. Jest to kluczowe ograniczenie obecnej implementacji: 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óry załącznik ma etykietę „Synchronizacja oceny” ani ile załączników ma konkretne zadanie.

Ustawianie maksymalnej oceny załącznika

W tej sekcji opisano ustawienie licznika dla oceny załącznika, 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ć drobną modyfikację naszej dotychczasowej implementacji. Podczas tworzenia załącznika dodaj wartość maxPoints w tym samym obiekcie AddOnAttachment, który zawiera pola studentWorkReviewUriteacherViewUri oraz inne pola załącznika.

Pamiętaj, że domyślny maksymalny wynik dla nowego projektu to 100. Zalecamy ustawienie wartości 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 scenariusza wartość maxPoints jest również przechowywana w lokalnej bazie danych załączników. Dzięki temu nie trzeba będzie później 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 prześlij wartość itemId do pola CourseWork.id.

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 pole max_points do tabeli Attachment. Jeśli zastosujesz się do naszego przykładu, 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 prośby 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 zmienia się.

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 uprawnień teacher użytkownikom będącym uczniami, ponieważ może to spowodować nieoczekiwane działanie podczas interakcji uczniów z dodatkiem, np. wczytywanie ramki iframe widoku nauczyciela zamiast ramki iframe widoku 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 poszczególnych języków:

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 Sprawdzania 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ć dane dotyczące ocen.

Aby wdrożyć to rozwiązanie, dodaj 1 dodatkowe wywołanie interfejsu API do istniejącej trasy weryfikacji 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 wartością AddOnAttachmentStudentSubmission w składowej żą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. Dzięki temu użytkownicy 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. Modyfikować rekordy bazy danych użytkownika, aby przechowywać token 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ę 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żytkowników 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ępowałeś(-aś) zgodnie z poradnikami, w schemacie tabeli User powinien już być zapisany token odświeżania. Zapisywanie 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 inna osoba niż zalogowany użytkownik, 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ę ustawienia pointsEarned jako nauczyciela w zajęciach. Możesz to zrobić na kilka sposobów:

  • Przechowuj lokalnie mapowanie danych logowania nauczyciela do identyfikatorów kursów. Pamiętaj jednak, że ten sam nauczyciel nie zawsze musi być 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 przechowuj 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 swojej 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 informacji, 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 musisz zadzwonić z danymi logowania nauczyciela pobranymi 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 jako nauczyciel projekt z załącznikiem typu aktywność, prześlij odpowiedź jako uczeń, a następnie otwórz przesłane przez niego treści w ramce przeglądu 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ę na przekazanie oceny, przed otwarciem ramki iframe Ocena za zadanie ucznia powinna już być widoczna 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 wyż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.