Oceny załączników i przebieg zwrotny

To szósty przewodnik w serii dotyczącej dodatków do Classroom.

W tym przewodniku zmodyfikujesz przykład z poprzedniego kroku, aby utworzyć załącznik z oceną typu aktywności. Możesz też automatycznie przekazywać ocenę z powrotem do Google Classroom, która pojawi się w dzienniku ocen nauczyciela jako ocena robocza.

Ten przewodnik różni się nieco od innych z tej serii tym, że dostępne są 2 sposoby przekazywania ocen z powrotem do Classroom. Obydwa mają duży wpływ na dewelopera i wygodę użytkownika. Warto wziąć pod uwagę oba te elementy podczas projektowania dodatku do Classroom. Dodatkowe omówienie opcji implementacji znajdziesz na stronie z przewodnikiem dotyczącym korzystania z załączników.

Pamiętaj, że funkcje oceniania dostępne w interfejsie API są opcjonalne. Można ich używać z każdym przyłączem typu activity.

W ramach tego przewodnika wykonasz następujące czynności:

  • Zmodyfikuj poprzednie żądania tworzenia załączników do interfejsu Classroom API, aby również ustawić mianownik ocen załącznika.
  • Automatycznie oceń zadanie przesłane przez ucznia i ustaw licznik oceny załącznika.
  • Stosuj 2 metody przekazywania oceny z zadania w Classroom przy użyciu danych logowania nauczyciela po zalogowaniu się lub w trybie offline.

Gdy skończysz, oceny pojawią się w dzienniku ocen Classroom po aktywowaniu działania przebiegu zwrotnego. Moment, w którym to nastąpi, zależy od metody wdrożenia.

Na potrzeby tego przykładu wykorzystaj aktywność z poprzedniego przejścia, w ramach której uczeń jest pokazywany na zdjęciu słynnego punktu orientacyjnego i otrzymuje prośbę o podanie jego nazwy. Jeśli uczeń podał prawidłowe imię i nazwisko, przypisz pełne znaczniki – w przeciwnym razie wpisz zero.

Omówienie funkcji oceniania dodatków w interfejsie Classroom

Dodatek może ustawić licznik ocen i mianownik ocen dla załącznika. Są one ustawiane odpowiednio za pomocą wartości pointsEarned i maxPoints w interfejsie API. Karta załącznika w interfejsie Classroom pokazuje ustawioną wartość maxPoints.

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

Rysunek 1. Interfejs tworzenia projektów z 3 kartami załączników ze skonfigurowanym ustawieniem maxPoints.

Interfejs API dodatków w Classroom umożliwia skonfigurowanie ustawień i określanie punktów za oceny załączników. Nie są one takie same jak oceny za projekt. Jednak ustawienia oceniania za projekt są zgodne z ustawieniami oceniania załącznikami z załącznika, który ma etykietę Synchronizacja ocen na karcie załącznika. Gdy załącznik „Synchronizacja ocen” ustawi pointsEarned dla zadania przesłanego przez ucznia, zostanie też ustawiona ocena robocza ucznia za ten projekt.

Zwykle pierwszy załącznik dodany do projektu, który ustawia maxPoints, otrzymuje etykietę „Synchronizacja ocen”. Przykład etykiety „Synchronizacja ocen” znajdziesz w interfejsie tworzenia projektów widocznym na Rysunku 1. Pamiętaj, że karta „Załącznik 1” ma etykietę „Synchronizacja ocen”, a ocena projektu w czerwonym polu została zmieniona do 50 punktów. Pamiętaj też, że chociaż na Rysunku 1 widać 3 karty załączników, tylko jedna z nich ma etykietę „Synchronizacja ocen”. Jest to kluczowe ograniczenie bieżącej implementacji: tylko jeden załącznik może mieć etykietę „Synchronizacja ocen”.

Jeśli plik maxPoints został ustawiony przez wiele załączników, usunięcie załącznika za pomocą „Synchronizacji ocen” nie włączy „synchronizacji ocen” dla pozostałych załączników. Dodanie kolejnego załącznika z ustawionym atrybutem maxPoints włącza synchronizację ocen w nowym załączniku, a maksymalna ocena za projekt jest dostosowywana do zgodności. Nie ma możliwości automatycznego sprawdzenia, który załącznik ma etykietę „Synchronizacja ocen”, ani liczby załączników w danym projekcie.

Ustawianie maksymalnej oceny za załącznik

W tej sekcji opisujemy, jak określić mianownik oceny za załącznik, czyli maksymalny możliwy wynik, jaki może uzyskać każdy uczeń za swoje zadanie. Aby to zrobić, ustaw wartość maxPoints załącznika.

Aby włączyć funkcje oceniania, wystarczy wprowadzić niewielką zmianę w dotychczasowej 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ślny maksymalny wynik nowego projektu to 100. Zalecamy ustawienie maxPoints na wartość inną niż 100, aby można było sprawdzić, czy oceny są ustawiane prawidłowo. W ramach demonstracji ustaw maxPoints na 50:

Python

Dodaj pole maxPoints podczas tworzenia obiektu attachment przed wysłaniem żądania CREATE do punktu końcowego courses.courseWork.addOnAttachments. Możesz go znaleźć w pliku webapp/attachment_routes.py, jeśli korzystasz z 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. Pozwala to uniknąć konieczności późniejszego 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ż w ten sposób itemId w polu CourseWork.id.

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

Python

Najpierw dodaj do tabeli Attachment pole max_points. Znajdziesz go w pliku webapp/models.py, jeśli korzystasz z naszego 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 prośby o courses.courseWork.addOnAttachments CREATE. Przechowywanie wartości maxPoints zwróconej 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ę. Już teraz możesz przetestować to działanie. Dodaj załącznik do nowego projektu i obserwuj, że na karcie załącznika widać etykietę „Synchronizacja ocen” i zmienia się wartość „Punkty” projektu.

Ustawianie oceny zadania przesłanego przez ucznia w Classroom

W tej sekcji opisano konfigurowanie licznika do oceny załącznika. Jest to wynik indywidualnego ucznia za załącznik. Aby to zrobić, ustaw wartość pointsEarned zadania ucznia.

Masz jedną ważną decyzję do podjęcia: w jaki sposób dodatek ma wysłać prośbę o ustawienie pointsEarned?

Problem polega na tym, że ustawienie pointsEarned wymaga zakresu OAuth teacher. Nie należy przypisywać uczniom zakresu teacher. Może to spowodować nieoczekiwane zachowanie, gdy uczniowie będą korzystać z dodatku, na przykład wczytanie elementu iframe Widok nauczyciela zamiast elementu iframe widoku ucznia. W związku z tym masz 2 możliwości skonfigurowania pointsEarned:

  • Przy użyciu danych logowania zalogowanego nauczyciela.
  • Korzystanie z zapisanych danych logowania nauczycieli (offline)

W sekcjach poniżej omawiamy wady każdego podejścia przed ich zademonstrowaniem. Podane przez nas przykłady pokazują oba sposoby przekazywania oceny w Classroom. Aby dowiedzieć się, jak wybrać metodę wyświetlania podanych przykładów, zapoznaj się z poniższymi instrukcjami dotyczącymi poszczególnych języków:

Python

Znajdź deklarację SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS na początku pliku webapp/attachment_routes.py. Ustaw tę wartość na True, aby przekazywać oceny przy użyciu danych logowania zalogowanego nauczyciela. Ustaw tę wartość na False, aby przekazywać oceny zwrotne przy użyciu zapisanych danych logowania, gdy uczeń przesyła zadanie.

Ustawianie ocen przy użyciu danych logowania nauczyciela

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

Weź jednak pod uwagę, że nauczyciel komunikuje się tylko z pracami ucznia w elemencie iframe sprawdzania zadań uczniów. Ma to ważne konsekwencje:

  • Oceny w Classroom nie są wypełniane, dopóki nauczyciel nie wykona działania w interfejsie Classroom.
  • Nauczyciel może chcieć otworzyć każde zadanie przesłane przez ucznia, aby uzupełnić wszystkie oceny.
  • Między otrzymaniem oceny a jej pojawieniem się w interfejsie Classroom występuje niewielkie opóźnienie. Opóźnienie wynosi zwykle 5–10 sekund, ale może wynosić nawet 30 sekund.

Połączenie tych czynników oznacza, że nauczyciele mogą być zmuszeni do wykonywania żmudnych i czasochłonnych zadań ręcznych, aby w pełni uzupełnić oceny w klasie.

Aby wdrożyć to podejście, dodaj 1 dodatkowe wywołanie interfejsu API do istniejącej trasy sprawdzania zadań uczniów.

Po pobraniu danych przesłanych przez ucznia oraz załączników sprawdź zadanie przesłane przez ucznia, a następnie zapisz 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 musimy też określić pointsEarned w 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()

Ustawianie ocen przy użyciu danych logowania nauczyciela offline

Drugie podejście do ustawiania ocen wymaga użycia przechowywanych danych logowania dla nauczyciela, który utworzył załącznik. Ta implementacja wymaga utworzenia danych logowania za pomocą tokenów odświeżania i dostępu do wcześniej autoryzowanego nauczyciela, a następnie użycia tych danych do skonfigurowania pointsEarned.

Kluczową zaletą tego podejścia jest to, że oceny są wypełniane w interfejsie Classroom bez konieczności podejmowania działań przez nauczyciela, dzięki czemu można uniknąć problemów wspomnianych powyżej. W efekcie użytkownicy uważają, że ocenianie jest łatwe i efektywne. Poza tym ta metoda pozwala wybrać moment, w którym przekazujesz oceny, na przykład kiedy uczniowie ukończą zadanie lub asynchronicznie.

Aby zastosować tę metodę, wykonaj te czynności:

  1. Zmodyfikuj rekordy bazy danych użytkownika, aby przechowywać token dostępu.
  2. Modyfikowanie rekordów bazy danych załączników w celu przechowywania identyfikatora nauczyciela.
  3. Pobranie danych logowania nauczyciela i (opcjonalnie) utworzenie nowej instancji usługi Classroom.
  4. ustawić ocenę za zadanie,

Na potrzeby tej prezentacji ustaw ocenę za wykonanie zadania, czyli gdy uczeń prześle formularz w ramach trasy widoku ucznia.

Modyfikowanie rekordów bazy danych użytkownika pod kątem przechowywania tokena dostępu

Do wywoływania interfejsu API są wymagane 2 unikalne tokeny: token odświeżania i token dostępu. Jeśli korzystasz z serii instrukcji do tej pory, schemat tabeli User powinien już zawierać token odświeżania. Przechowywanie tokena odświeżania jest wystarczające, jeśli wykonujesz wywołania interfejsu API tylko z zalogowanym użytkownikiem, ponieważ token dostępu otrzymujesz w ramach procesu uwierzytelniania.

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

Python

W podanym 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 dowolny kod, który tworzy lub aktualizuje rekord User, tak aby zawierał też token dostępu:

Python

W podanym 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 skonfigurować ocenę za zadanie, zadzwoń do: pointsEarned jest nauczycielem na zajęciach. Można to zrobić na kilka sposobów:

  • Zapisuj lokalne mapowanie danych logowania nauczyciela na identyfikatory zajęć. Pamiętaj jednak, że ten sam nauczyciel nie zawsze jest powiązany z konkretnym kursem.
  • Wyślij żądania GET do punktu końcowego courses interfejsu Classroom API, aby pobrać bieżących nauczycieli. Następnie można wyszukać w lokalnych rekordach użytkowników zgodne 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 pliku attachmentId przekazanego do elementu iframe widoku ucznia.

Ten przykład pokazuje ostatnią opcję, ponieważ ustawiasz oceny, gdy uczeń wykona załącznik do zadania.

Dodaj pole identyfikatora nauczyciela do tabeli Attachment bazy danych:

Python

W podanym 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, tak aby zawierał też identyfikator twórcy:

Python

W podanym 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 wysyła element iframe widoku ucznia. Natychmiast po zapisaniu odpowiedzi ucznia w lokalnej bazie danych pobierz dane logowania nauczyciela z pamięci lokalnej. Nie powinno to być trudne, biorąc pod uwagę przygotowanie w poprzednich 2 krokach. Za ich pomocą można też utworzyć nową instancję 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,
    discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
    credentials=teacher_credentials)

Ustawianie oceny za zadanie

Procedura jest taka sama jak w przypadku korzystania z danych logowania zalogowanego nauczyciela. Pamiętaj jednak, że musisz wykonać wywołanie 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 przewodniku utwórz jako nauczyciel projekt z załącznikiem do działania, prześlij odpowiedź jako uczeń, a następnie otwórz projekt w elemencie iframe z oceną zadań uczniów. Ocena powinna pojawić się w różnych momentach w zależności od zastosowanej metody:

  • Jeśli zdecydujesz się na zaliczenie zadania przez ucznia za wykonanie zadania, przed otwarciem elementu iframe z oceną zadania ucznia powinna być już widoczna w interfejsie. Możesz go 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” wkrótce po wczytaniu elementu iframe. Jak wspomnieliśmy powyżej, może to potrwać do 30 sekund. Następnie ocena konkretnego ucznia powinna być też widoczna w innych widokach dziennika ocen Classroom.

Sprawdź, czy uczniowi wyświetla się poprawny wynik.

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