Oceny załączników i przebieg zwrotny

To szósta instrukcja z serii instrukcji dotyczących dodatków do Classroom.

W tym przewodniku zmodyfikujesz przykład z poprzedniego kroku, aby utworzyć załącznik typu aktywność z oceną. Możesz też przekazać ocenę z powrotem do Google Classroom za pomocą programu, który wyświetla ją w dzienniku nauczyciela jako ocenę w wersji roboczej.

Ten przewodnik różni się nieco od innych z tej serii, ponieważ przedstawia 2 możliwe sposoby przekazywania ocen do Classroom. Obie mają różny wpływ na wrażenia dewelopera i użytkownika. Weź pod uwagę oba te aspekty podczas projektowania dodatku do Classroom. Więcej informacji o opcjach implementacji znajdziesz na naszej stronie przewodnika Interakcja z załącznikami.

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

W ramach tego przewodnika wykonasz te czynności:

  • Zmodyfikuj poprzednie żądania utworzenia załącznika w interfejsie Classroom API, aby ustawić też mianownik oceny załącznika.
  • Programowe ocenianie przesłanego projektu ucznia i ustawianie licznika oceny załącznika.
  • Wdróż 2 podejścia, aby przekazać ocenę przesłanego zadania do Classroom przy użyciu danych logowania nauczyciela lub danych logowania offline.

Po zakończeniu oceniania oceny pojawią się w dzienniku ocen w Classroom po wywołaniu zachowania przekazywania. Dokładny moment, w którym to nastąpi, zależy od podejścia do implementacji.

Na potrzeby tego przykładu użyj ponownie aktywności z poprzedniego przewodnika, w której uczeń widzi obraz znanego zabytku i jest proszony o podanie jego nazwy. Przyznaj pełną liczbę punktów za załącznik, jeśli uczeń wpisze prawidłową nazwę, a w przeciwnym razie przyznaj 0 punktów.

Informacje o funkcji oceniania w interfejsie API Dodatków do Classroom

Dodatek może ustawić zarówno licznik, jak i mianownik oceny załącznika. Są one odpowiednio ustawiane za pomocą wartości pointsEarnedmaxPoints w interfejsie API. Karta załącznika w interfejsie Classroom wyświetla wartość maxPoints, jeśli została ona ustawiona.

Przykład wielu załączników z wartością maxPoints w przypadku jednego zadania

Rysunek 1. Interfejs tworzenia zadania z 3 kartami załączników dodatków, które mają ustawioną wartość maxPoints.

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

Zazwyczaj pierwsza załączona do zadania praca, która ma ustawioną maxPoints punktację, otrzymuje etykietę „Synchronizacja ocen”. Przykład etykiety „Synchronizacja ocen” znajdziesz na ilustracji 1, która przedstawia interfejs tworzenia projektu. Zwróć uwagę, że karta „Załącznik 1” ma etykietę „Synchronizacja ocen”, a ocena projektu w czerwonym polu została zaktualizowana do 50 punktów. Zwróć też uwagę, że chociaż na ilustracji 1 widać 3 karty załączników, tylko jedna z nich ma etykietę „Synchronizacja ocen”. Jest to kluczowe ograniczenie obecnego wdrożenia: tylko jeden załącznik może mieć etykietę „Synchronizacja ocen”.

nauczyciele byli wyraźnie informowani o oczekiwanym zachowaniu w przypadku dodania więcej niż 1 załącznika obsługującego przekazywanie ocen.

Jeśli jest kilka załączników z ustawioną wartością maxPoints, usunięcie załącznika z włączoną synchronizacją ocen nie spowoduje włączenia synchronizacji ocen w żadnym z pozostałych załączników. Dodanie kolejnego załącznika, który ustawia maxPoints, włącza synchronizację ocen w nowym załączniku, a maksymalna ocena za zadanie dostosowuje się do tego ustawienia. Nie ma mechanizmu, który umożliwiałby automatyczne sprawdzenie, który załącznik ma etykietę „Synchronizacja ocen”, ani sprawdzenie, ile załączników ma dane zadanie.

Ustawianie maksymalnej oceny załącznika

W tej sekcji opisujemy ustawianie mianownika oceny za załącznik, czyli maksymalnej możliwej liczby punktów, jaką wszyscy uczniowie mogą uzyskać za przesłane prace. Aby to zrobić, ustaw wartość maxPoints załącznika.

Aby włączyć funkcje oceniania, wystarczy wprowadzić niewielkie zmiany w dotychczasowej implementacji. Podczas tworzenia załącznika dodaj wartość maxPoints w tym samym AddOnAttachment obiekcie, który zawiera pola studentWorkReviewUri, teacherViewUri i inne pola załącznika.

Pamiętaj, że domyślna maksymalna liczba punktów za nowe zadanie to 100. Sugerujemy ustawienie wartości maxPoints innej niż 100, aby można było sprawdzić, czy oceny są ustawiane prawidłowo. Ustaw wartość maxPoints na 50, aby zademonstrować działanie:

Python

Dodaj pole maxPoints podczas tworzenia obiektu attachment, tuż przed wysłaniem żądania CREATE do courses.courseWork.addOnAttachments. Jeśli korzystasz z naszego przykładu, znajdziesz go w pliku webapp/attachment_routes.py.

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 przechowujesz też wartość maxPoints w lokalnej bazie danych załączników. Dzięki temu nie musisz później wykonywać dodatkowego wywołania interfejsu API podczas oceniania prac uczniów. Pamiętaj jednak, że nauczyciele mogą zmieniać ustawienia oceniania projektów niezależnie od Twojego dodatku. Wyślij żądanie GET do courses.courseWork punktu końcowego, aby zobaczyć wartość maxPoints na poziomie przypisania. W tym celu przekaż wartość itemId w polu CourseWork.id.

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

Python

Najpierw dodaj pole max_points do tabeli Attachment. Jeśli korzystasz z 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 już teraz przetestować to działanie: dodaj załącznik do nowego zadania i sprawdź, czy na karcie załącznika wyświetla się etykieta „Synchronizacja ocen”, a wartość „Punkty” zadania uległa zmianie.

Ustawianie oceny za przesłane zadanie ucznia w Classroom

W tej sekcji opisano ustawianie licznika oceny załącznika, czyli oceny załącznika uzyskanej przez poszczególnych uczniów. Aby to zrobić, ustaw wartość pointsEarned przesłanego przez ucznia załącznika.

Musisz teraz podjąć ważną decyzję: w jaki sposób dodatek powinien wysyłać żądanie ustawienia pointsEarned?

Problem polega na tym, że ustawienie pointsEarned wymaga zakresu OAuth teacher. Nie przyznawaj uczniom zakresu teacher, ponieważ może to spowodować nieoczekiwane zachowanie, gdy uczniowie będą wchodzić w interakcję z dodatkiem, np. załadowanie ramki iframe widoku nauczyciela zamiast ramki iframe widoku ucznia. Dlatego masz 2 możliwości ustawienia wartości pointsEarned:

  • używając danych logowania zalogowanego nauczyciela;
  • Korzystanie z zapisanych (offline) danych logowania nauczyciela.

W kolejnych sekcjach omówimy zalety i wady każdego podejścia, a następnie pokażemy, jak je wdrożyć. Pamiętaj, że podane przykłady pokazują oba sposoby przekazywania oceny do Classroom. Aby dowiedzieć się, jak wybrać sposób podczas uruchamiania podanych przykładów, zapoznaj się z instrukcjami w konkretnym języku poniżej:

Python

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

Ustawianie ocen przy użyciu danych logowania zalogowanego nauczyciela

Użyj danych logowania zalogowanego użytkownika, aby wysłać żądanie ustawienia pointsEarned. Powinno to być dość intuicyjne, ponieważ odzwierciedla resztę dotychczasowej implementacji i nie wymaga wiele wysiłku.

Pamiętaj jednak, że nauczyciel tylko wchodzi w interakcję z przesłanym przez ucznia zadaniem w ramce iframe sprawdzania prac uczniów. Ma to ważne konsekwencje:

  • Oceny nie pojawią się w Classroom, dopóki nauczyciel nie wykona działania w interfejsie Classroom.
  • Nauczyciel może musieć otworzyć wszystkie przesłane przez uczniów projekty, aby dodać oceny wszystkich uczniów.
  • Między otrzymaniem oceny przez Classroom a jej pojawieniem się w interfejsie Classroom występuje niewielkie opóźnienie. Opóźnienie wynosi zwykle od 5 do 10 sekund, ale może sięgać nawet 30 sekund.

Połączenie tych czynników oznacza, że nauczyciele mogą być zmuszeni do wykonania znacznej ilości czasochłonnej pracy ręcznej, aby w pełni wypełnić oceny w klasie.

Aby wdrożyć to podejście, dodaj jedno wywołanie interfejsu API do istniejącej ścieżki sprawdzania prac uczniów.

Po pobraniu przesłanego przez ucznia projektu i rekordów załączników oceń projekt ucznia i zapisz wynikającą z tego ocenę. Ustaw ocenę w polu pointsEarned obiektu AddOnAttachmentStudentSubmission. Na koniec wyślij żądanie PATCH do punktu końcowego courses.courseWork.addOnAttachments.studentSubmissions z instancją AddOnAttachmentStudentSubmission w treści żądania. Pamiętaj, że w żądaniu PATCH musimy też określić pointsEarnedupdateMask:

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 w trybie offline

Drugie podejście do ustawiania 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 wcześniej autoryzowanego 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 nauczyciela w interfejsie Classroom, co pozwala uniknąć wspomnianych powyżej problemów. Dzięki temu użytkownicy postrzegają proces oceniania jako płynny i wydajny. Dodatkowo to podejście pozwala wybrać moment, w którym zwracasz oceny, np. gdy uczniowie ukończą aktywność lub asynchronicznie.

Aby wdrożyć to podejście, 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. Ustaw ocenę przesłanej pracy.

Na potrzeby tej demonstracji ustaw ocenę, gdy uczeń ukończy zadanie, czyli gdy prześle formularz w widoku ucznia.

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

Do wykonywania wywołań interfejsu API wymagane są 2 unikalne tokeny: token odświeżaniatoken dostępu. Jeśli do tej pory korzystasz z tej serii przewodników, Userschemat tabeli powinien już przechowywać token odświeżania. Przechowywanie tokena odświeżania wystarczy, jeśli wywołujesz interfejs API tylko w imieniu zalogowanego użytkownika, ponieważ token dostępu otrzymujesz w ramach procesu uwierzytelniania.

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

Python

W naszym przykładzie jest to plik 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 jest to plik 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ść, musisz mieć uprawnienia pointsEarned nauczyciela w kursie. Możesz to zrobić na kilka sposobów:

  • przechowywać lokalne mapowanie danych logowania nauczyciela na identyfikatory kursów; Pamiętaj jednak, że ten sam nauczyciel nie zawsze musi być powiązany z konkretnym kursem.
  • Wysyłaj żądania GET do punktu końcowego courses interfejsu Classroom API, aby uzyskać informacje o bieżących nauczycielach. Następnie wyszukaj w lokalnych rekordach użytkowników pasujące dane logowania nauczyciela.
  • Podczas tworzenia załącznika do dodatku zapisz identyfikator nauczyciela w lokalnej bazie danych załączników. Następnie pobierz dane logowania nauczyciela z elementu attachmentId przekazanego do elementu iframe Widok ucznia.

Ten przykład pokazuje ostatnią opcję, ponieważ oceny są ustawiane, gdy uczeń ukończy załącznik z aktywnością.

Dodaj do tabeli Attachment w bazie danych pole identyfikatora nauczyciela:

Python

W naszym przykładzie jest to plik 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 jest to metoda 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ź trasę, która obsługuje element iframe widoku ucznia. Natychmiast po zapisaniu odpowiedzi ucznia w lokalnej bazie danych pobierz z lokalnego miejsca na dane dane logowania nauczyciela. Biorąc pod uwagę przygotowania z 2 poprzednich kroków, powinno to być proste. Możesz też użyć tych informacji do utworzenia nowej instancji usługi Classroom dla nauczyciela:

Python

W podanym przykładzie jest to metoda 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łanej pracy

Dalsza procedura jest identyczna jak w przypadku używania danych logowania zalogowanego nauczyciela. Pamiętaj jednak, że połączenie należy nawiązać przy użyciu danych logowania nauczyciela uzyskanych 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 przykładzie utwórz projekt z załącznikiem typu aktywność jako nauczyciel, prześlij odpowiedź jako uczeń, a następnie otwórz przesłany projekt w elemencie iframe do sprawdzania prac uczniów. W zależności od sposobu wdrożenia ocena może pojawiać się w różnym czasie:

  • Jeśli po ukończeniu przez ucznia aktywności została przekazana ocena, w interfejsie powinna być już widoczna ocena w wersji roboczej przed otwarciem ramki iframe z przeglądem pracy ucznia. Możesz też zobaczyć ją na liście uczniów po otwarciu projektu oraz w polu „Ocena” obok ramki iframe „Sprawdzanie zadania ucznia”.
  • Jeśli zdecydujesz się przekazać ocenę, gdy nauczyciel otworzy ramkę iframe do sprawdzania zadań uczniów, ocena powinna pojawić się w polu „Ocena” wkrótce po załadowaniu ramki iframe. Jak wspomnieliśmy powyżej, może to potrwać do 30 sekund. Następnie ocena konkretnego ucznia powinna pojawić się również w innych widokach dziennika w Classroom.

Sprawdź, czy uczeń ma prawidłowy wynik.

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