Oceny załączników i przebieg zwrotny

Oto szósty przewodnik z serii przewodników dotyczących dodatków do Classroom.

W tym przewodniku zmodyfikujesz przykład z poprzedniego kroku, aby utworzyć załącznik typu działania o ocenie. Automatycznie przesyłasz też ocenę z powrotem do Google Classroom. Pojawia się ona w dzienniku ocen nauczyciela jako ocena robocza.

Ten przewodnik nieco różni się od innych w tej serii, że istnieją dwa sposoby przekazywania ocen z powrotem do Classroom. Oba te rodzaje mają różny wpływ na środowisko programistyczne i sposób korzystania z nich. Weź pod uwagę je podczas projektowania dodatku do Classroom. Dodatkowe omówienie opcji implementacji znajdziesz w przewodniku po interakcji z załącznikami.

Pamiętaj, że funkcje oceniania w interfejsie API są opcjonalne. Można ich używać z dowolnym załącznikiem typu działania.

W tym przewodniku wykonasz te czynności:

  • Zmodyfikuj poprzednie żądania tworzenia załączników do interfejsu Classroom API, aby ustawić mianownik ocen załącznika.
  • Automatycznie oceniaj zadania przesłane przez uczniów i ustaw licznik oceny za załącznik.
  • Zaimplementuj 2 sposoby przekazywania oceny zadanej do Classroom za pomocą zalogowanych lub offline danych logowania nauczyciela.

Po zakończeniu zadania oceny pojawią się w dzienniku ocen Classroom po aktywowaniu działania przebiegu zwrotnego. Dokładny moment, w którym to nastąpi, zależy od podejścia.

Na potrzeby tego przykładu wykorzystaj ponownie aktywność z poprzedniej instrukcji, w której uczeń pokazuje obraz znanego punktu orientacyjnego, a uczniowie otrzymują prośbę o wpisanie jego imienia i nazwiska. Jeśli uczeń wpisze poprawne imię i nazwisko, przypisz do załącznika pełne oceny. W przeciwnym razie wpisz 0.

Informacje o funkcji oceniania dodatków w interfejsie API dodatków w Classroom

Dodatek może ustawić licznik i mianownik ocen załącznika. Ustawia się je odpowiednio za pomocą wartości pointsEarned i maxPoints w interfejsie API. Na karcie załącznika w interfejsie Classroom pokazuje się wartość maxPoints, jeśli została ustawiona.

Przykład wielu załączników z maxPoints w jednym projekcie

Rysunek 1. Interfejs tworzenia projektów z 3 kartami załączników dodatków, dla których ustawiono maxPoints.

Interfejs API dodatków do Classroom umożliwia konfigurowanie ustawień i określanie wyników uzyskanych za oceny związane z załącznikami. To nie to samo co oceny za projekt. Ustawienia ocen za projekt są jednak zgodne z ustawieniami oceny za załącznik, który ma etykietę Synchronizacja ocen na karcie załącznika. Gdy załącznik „Synchronizacja ocen” ustawia wartość pointsEarned za zadanie przesłane przez ucznia, ustawia też ocenę roboczą tego projektu.

Zazwyczaj etykietę „Synchronizacja ocen” otrzymuje zazwyczaj pierwszy załącznik dodany do projektu, w którym ustawiono maxPoints. Przykładowy interfejs tworzenia projektu na rys. 1 zawiera przykładową etykietę „Synchronizacja ocen”. Pamiętaj, że karta „Załącznik 1” ma etykietę „Synchronizacja ocen”, a ocena za projekt w czerwonym polu została zmieniona na 50 punktów. Na ilustracji 1 widać 3 karty załączników, ale tylko jedna z nich ma etykietę „Synchronizacja ocen”. Jest to główne ograniczenie bieżącej implementacji: tylko jeden załącznik może mieć etykietę „Synchronizacja ocen”.

Jeśli istnieje wiele załączników, dla których ustawiono maxPoints, usunięcie załącznika z opcją „Synchronizacja ocen” nie spowoduje włączenia tej funkcji w żadnym z pozostałych załączników. Dodanie kolejnego załącznika, który ustawia element maxPoints, spowoduje włączenie synchronizacji ocen w nowym załączniku. Maksymalna ocena za projekt zostanie dostosowana do. Nie można automatycznie sprawdzić, który załącznik ma etykietę „Synchronizacja ocen”, ani sprawdzić, ile załączników ma dany projekt.

Ustawianie maksymalnej oceny załącznika

W tej sekcji opisano ustawianie mianownika oceny za załącznik, czyli maksymalnego możliwego wyniku, który wszyscy uczniowie mogą uzyskać za zadanie. Aby to zrobić, ustaw wartość maxPoints załącznika.

Aby włączyć funkcje oceniania, potrzebna jest tylko niewielka zmiana w obecnej implementacji. Podczas tworzenia załącznika dodaj wartość maxPoints w tym samym obiekcie AddOnAttachment, który zawiera pola studentWorkReviewUri, teacherViewUri i inne.

Pamiętaj, że domyślna maksymalna liczba punktów dla nowego projektu to 100. Sugerujemy ustawienie maxPoints na wartość inną niż 100, by umożliwić sprawdzenie, czy oceny są ustawiane prawidłowo. Ustaw maxPoints na 50 w ramach demonstracji:

Python

Dodaj pole maxPoints podczas tworzenia obiektu attachment, jeszcze przed wysłaniem żądania CREATE do punktu końcowego courses.courseWork.addOnAttachments. Znajdziesz go w pliku webapp/attachment_routes.py, jeśli użyjesz podanego 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 tej demonstracji zapisujesz również wartość maxPoints w lokalnej bazie danych załączników. Dzięki temu nie musisz później przeprowadzać dodatkowego wywołania interfejsu API podczas oceniania prac uczniów. Pamiętaj jednak, że nauczyciele mogą zmienić ustawienia ocen za projekt niezależnie od Twojego dodatku. Wyślij żądanie GET do punktu końcowego courses.courseWork, aby wyświetlić wartość maxPoints na poziomie przypisania. Przekaż przy tym itemId w polu CourseWork.id.

Teraz zaktualizuj model bazy danych, aby obsługiwał też wartość maxPoints załącznika. Zalecamy użycie wartości maxPoints z odpowiedzi CREATE:

Python

Najpierw dodaj pole max_points do tabeli Attachment. Znajdziesz go w pliku webapp/models.py, jeśli użyjesz podanego przykładu.

# 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 żądania courses.courseWork.addOnAttachments CREATE. Zapisz wartość maxPoints zwrócona 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 już teraz przetestować to zachowanie. Możesz dodać załącznik do nowego projektu i zobaczyć, że na karcie załącznika wyświetla się etykieta „Synchronizacja ocen” i zmiana wartości „Punkty” projektu.

Ustawianie oceny przesłanej przez ucznia w Classroom

W tej sekcji opisano ustawianie licznika do oceny załącznika, czyli wyniku pojedynczego ucznia dotyczącego załącznika. Aby to zrobić, ustaw wartość pointsEarned zgłoszenia ucznia.

Teraz musisz podjąć ważną decyzję: w jaki sposób Twój dodatek powinien wysłać prośbę o ustawienie pointsEarned?

Problem polega na tym, że ustawienie pointsEarned wymaga zakresu protokołu OAuth teacher. Nie przyznawaj uczniom zakresu teacher. Może to spowodować nieoczekiwane zachowanie uczniów podczas interakcji z dodatkiem. Na przykład może to spowodować wczytywanie przez nich elementu iframe widoku nauczyciela zamiast elementu iframe widoku ucznia. Dlatego możesz ustawić pointsEarned na 2 sposoby:

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

W sekcjach poniżej omawiamy wady każdego podejścia, zanim zademonstrujesz poszczególne implementacje. Przedstawione przykłady przedstawiają oba metody przekazywania ocen w Classroom. Zapoznaj się z poniższymi instrukcjami dla poszczególnych języków, aby wybrać metodę podczas uruchamiania podanych przykładów:

Python

Na górze pliku webapp/attachment_routes.py znajdź deklarację SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS. Ustaw tę wartość na True, aby przesyłać oceny z użyciem danych logowania zalogowanego nauczyciela. Ustaw tę wartość na False, aby przekazywać oceny przy użyciu zapisanych danych logowania, gdy uczeń przesyła zadanie.

Wyznaczanie ocen za pomocą danych logowania zalogowanego nauczyciela

Użyj danych logowania zalogowanego użytkownika, aby wysłać prośbę o ustawienie pointsEarned. Powinno to wydawać się intuicyjne, ponieważ odzwierciedla pozostałą część dotychczasowej implementacji i wymaga niewielkiego nakładu pracy.

Pamiętaj jednak, że nauczyciel wchodzi w interakcję tylko z treścią ucznia w elemencie iframe z oceną pracy ucznia. Ma to kilka ważnych konsekwencji:

  • Oceny nie są wypełniane w Classroom, dopóki nauczyciel nie podejmie działania w interfejsie Classroom.
  • Aby wypełnić wszystkie oceny uczniów, nauczyciel może otworzyć wszystkie zadania przesłane przez ucznia.
  • Występuje niewielkie opóźnienie między otrzymaniem oceny przez Classroom a jej pojawieniem się w interfejsie Classroom. Opóźnienie wynosi zwykle 5–10 sekund, ale może wynosić nawet 30 sekund.

Połączenie tych czynników oznacza, że nauczyciele będą musieli wykonać sporą, czasochłonną pracę ręczną, aby w pełni wypełnić oceny uczniów.

Aby wdrożyć tę metodę, dodaj jedno dodatkowe wywołanie interfejsu API do istniejącej trasy sprawdzania pracy uczniów.

Po pobraniu zadań ucznia i zapisów załączników oceń zadanie przesłane przez ucznia i zapisz wynikową ocenę. Ustaw ocenę w polu pointsEarned obiektu AddOnAttachmentStudentSubmission. Na koniec wyślij żądanie PATCH do punktu końcowego courses.courseWork.addOnAttachments.studentSubmissions, używając instancji AddOnAttachmentStudentSubmission w treści żądania. Pamiętaj, że musimy również określić pointsEarned w elemencie updateMask w żądaniu PATCH:

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()

Określanie ocen przy użyciu danych logowania nauczyciela offline

Drugi sposób określania ocen wymaga użycia zapisanych danych logowania nauczyciela, który utworzył załącznik. Ta implementacja wymaga utworzenia danych logowania przy użyciu tokenów odświeżania i dostępu autoryzowanych wcześniej nauczyciela, a następnie użycia tych danych logowania do ustawienia pointsEarned.

Kluczową zaletą tego podejścia jest to, że oceny są wypełniane bez konieczności podejmowania działań przez nauczycieli w interfejsie Classroom, co pozwala uniknąć problemów wymienionych powyżej. W efekcie użytkownicy postrzegają proces oceniania jako bezproblemowy i wydajny. Poza tym to podejście pozwala wybrać moment, w którym zwracasz oceny, np. gdy uczniowie wykonali zadanie lub asynchronicznie.

Aby wdrożyć tę metodę, wykonaj te czynności:

  1. Zmodyfikuj rekordy bazy danych użytkowników, aby przechowywać token dostępu.
  2. Zmodyfikuj 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. wystawić ocenę za zadanie.

Na potrzeby tej demonstracji należy ustawić ocenę za wykonanie zadania przez ucznia, czyli za przesłanie formularza w ramach trasy Widok ucznia.

Zmodyfikuj rekordy bazy danych użytkowników, aby przechowywać token dostępu

Do wykonywania wywołań interfejsu API są wymagane 2 unikalne tokeny: token odświeżania i token dostępu. Jeśli do tej pory korzystasz z serii przewodników, Twój schemat tabeli User powinien już przechowywać token odświeżania. Przechowywanie tokena odświeżania jest wystarczające, gdy wywołujesz interfejs API tylko zalogowanego użytkownika, ponieważ otrzymujesz token dostępu w ramach procesu uwierzytelniania.

Teraz musisz jednak wykonywać połączenia w imieniu innej osoby niż zalogowany użytkownik. Oznacza to, że proces uwierzytelniania jest niedostępny. Musisz więc przechowywać token dostępu razem z tokenem odświeżania. Zaktualizuj schemat tabeli User, tak aby zawierał token dostępu:

Python

W naszym przykładzie znajduje się to 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, tak aby przechowywał też token dostępu:

Python

W naszym przykładzie znajduje się to 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()

Zmodyfikuj rekordy bazy danych załączników, aby przechowywać identyfikator nauczyciela

Aby ustawić ocenę za zadanie, wywołaj ustawienie użytkownika pointsEarned jako nauczyciela zajęć. Możesz to zrobić na kilka sposobów:

  • Przechowuj lokalne mapowanie danych logowania nauczycieli na identyfikatory zajęć. Pamiętaj jednak, że ten sam nauczyciel nie zawsze jest powiązany z konkretnymi zajęciami.
  • Wyślij żądania GET do punktu końcowego courses interfejsu Classroom API, aby pobrać obecnych nauczycieli. Następnie można wysłać zapytanie do lokalnych rekordów użytkowników, aby znaleźć pasujące dane logowania nauczycieli.
  • Podczas tworzenia załącznika dodatku zapisz identyfikator nauczyciela w lokalnej bazie danych załączników. Następnie pobierz dane logowania nauczyciela z elementu attachmentId przekazywanego do elementu iframe widoku ucznia.

Ten przykład przedstawia ostatnią opcję, ponieważ ustawiasz oceny, gdy uczeń ukończy zadanie.

Dodaj pole identyfikatora nauczyciela do tabeli Attachment w bazie danych:

Python

W naszym przykładzie znajduje się to 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, tak aby przechowuje też identyfikator twórcy:

Python

W podanym przykładzie znajduje się to 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

Odszukaj trasę, która korzysta z elementu iframe widoku ucznia. Natychmiast po zapisaniu odpowiedzi ucznia w lokalnej bazie danych pobierz dane logowania nauczyciela z lokalnej pamięci masowej. Powinno to być proste, biorąc pod uwagę przygotowania w poprzednich 2 krokach. Możesz też użyć tych elementów do utworzenia nowej instancji usługi Classroom dla użytkownika:

Python

W podanym przykładzie znajduje się to 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,
    discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
    credentials=teacher_credentials)

Ustawianie oceny zadania

Ta procedura jest identyczna jak w przypadku korzystania z danych logowania zalogowanego nauczyciela. Pamiętaj jednak, że musisz to zrobić przy użyciu 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 przewodniku utwórz projekt z załącznikiem aktywności jako nauczyciel, prześlij odpowiedź jako uczeń, a następnie otwórz zadanie w elemencie iframe z oceną zadań uczniów. Ocena powinna być widoczna w różnych momentach w zależności od wybranego sposobu wdrożenia:

  • Jeśli zdecydujesz się przekazać ocenę po wykonaniu przez ucznia zadania, jego wersja robocza powinna już być widoczna w interfejsie użytkownika, zanim otworzysz element iframe Sprawdzanie zadań. Możesz ją też zobaczyć na liście uczniów po otwarciu projektu oraz w polu „Ocena” obok elementu iframe z oceną zadań uczniów.
  • Jeśli zdecydujesz się przekazać ocenę, gdy nauczyciel otworzy element iframe z oceną zadań uczniów, ocena powinna pojawić się w polu „Ocena” zaraz po wczytaniu elementu iframe. Jak wspomniano powyżej, może to potrwać do 30 sekund. Ocena dla konkretnego ucznia powinna także pojawić się w innych widokach dziennika ocen Classroom.

Sprawdź, czy uczeń widzi właściwy wynik.

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