To szósty z kolei przewodnik z serii o dodatkach do Classroom.
W tym samouczku zmodyfikujesz przykład z poprzedniego kroku, aby utworzyć załącznik typu aktywność z ocenianiem. Możesz też przekazać ocenę z powrotem do Google Classroom za pomocą programowania. Ocena pojawi się w dzienniku ocen nauczyciela jako wersja robocza.
Ten przewodnik różni się nieco od innych w serii, ponieważ przedstawia 2 możliwe podejścia do przekazywania ocen do Google Classroom. Oba mają odmienny wpływ na wrażenia dewelopera i użytkownika. Pamiętaj o obu tych kwestiach podczas projektowania dodatku do Google Classroom. Więcej informacji o opcjach implementacji znajdziesz w przewodniku Interakcja z załącznikami.
Pamiętaj, że funkcje oceniania w interfejsie API są opcjonalne. Można ich używać w przypadku dowolnego załącznika typu aktywność.
W ramach tego samouczka wykonasz te czynności:
- Zmodyfikuj poprzednie żądania tworzenia załączników do interfejsu Classroom API, aby ustawić także licznik oceny załącznika.
- Programowo ocenić przesłane przez ucznia treści i ustawić licznik oceny za załącznik.
- Wprowadź 2 metody przekazywania oceny przesłanego projektu do Classroom za pomocą zalogowanych lub offline danych logowania nauczyciela.
Po zakończeniu zadania oceny pojawią się w dzienniku ocen w Classroom po uruchomieniu przekierowania. Dokładny moment, w którym to nastąpi, zależy od podejścia.
W tym przykładzie użyj ponownie aktywności z poprzedniego omówienia, w której uczniowi wyświetla się obraz słynnego zabytku i prosi o wpisanie jego nazwy. Przypisz pełną liczbę punktów za załącznik, jeśli uczeń wprowadzi prawidłową nazwę, a w przeciwnym razie zero.
Funkcja oceniania w interfejsie API dodatków do Classroom
Twój dodatek może ustawić zarówno licznik, jak i mianownik oceny za załącznik. Są one odpowiednio ustawiane za pomocą wartości pointsEarned
i maxPoints
w interfejsie API. Gdy wartość maxPoints
zostanie ustawiona, karta załącznika w interfejsie Classroom będzie ją wyświetlać.
Rysunek 1. Interfejs tworzenia projektu z 3 kartami załączników dodatku z ustawioną wartością maxPoints
.
Interfejs Classroom API umożliwia konfigurowanie ustawień i określanie punktów przyznawanych za załączniki. To nie to samo co oceny za projekt. Ustawienia oceny projektu są jednak zgodne z ustawieniami oceny załącznika, który ma etykietę Synchronizacja ocen na karcie załącznika. Gdy załącznik „Synchronizacja ocen” ustawia wartość pointsEarned
dla przesłanego przez ucznia projektu, ustawia też jego wersję roboczą oceny za projekt.
Zwykle pierwszy załącznik dodany do projektu, który określamaxPoints
, otrzymuje etykietę „Synchronizacja ocen”. Przykładowy interfejs tworzenia projektu na rys. 1 zawiera przykładową etykietę „Synchronizacja ocen”. Zwróć uwagę, że karta „Załącznik 1” ma etykietę „Synchronizacja oceny”, a ocena projektu w czerwonym polu została zaktualizowana do 50 punktów. Na ilustracji 1 widać 3 karty załączników, ale tylko jedna z nich ma etykietę „Synchronizacja ocen”. Jest to kluczowe ograniczenie obecnego rozwiązania: tylko jeden załącznik może mieć etykietę „Synchronizacja ocen”.
Jeśli masz wiele załączników z ustawieniem maxPoints
, usunięcie załącznika z ustawieniem „Synchronizacja ocen” nie spowoduje włączenia opcji „Synchronizacja ocen” w przypadku pozostałych załączników. Dodanie kolejnego załącznika, który ustawia wartość maxPoints
, powoduje włączenie synchronizacji ocen w przypadku nowego załącznika, a maksymalna ocena projektu zostanie dostosowana. Nie ma mechanizmu, który pozwalałby sprawdzić programowo, które załączniki mają etykietę „Synchronizacja ocen”, ani ile załączników ma konkretne zadanie.
Ustawianie maksymalnej oceny załącznika
W tej sekcji opisano ustawienie licznika dla oceny za załącznik, czyli maksymalnej możliwej liczby punktów, jaką wszyscy uczniowie mogą uzyskać za swoje przesłane treści. Aby to zrobić, ustaw wartość maxPoints
załącznika.
Aby włączyć funkcje oceniania, wystarczy wprowadzić tylko drobną modyfikację naszej dotychczasowej implementacji. Podczas tworzenia załącznika dodaj wartość maxPoints
w tym samym obiekcie AddOnAttachment
, który zawiera pola studentWorkReviewUri
i teacherViewUri
oraz inne pola załącznika.
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. 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 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ą zmieniać ustawienia oceny projektu niezależnie od dodatku. Aby wyświetlić wartość maxPoints
na poziomie projektu, prześlij żądanie GET
do punktu końcowego courses.courseWork
. W tym celu w polu CourseWork.id
prześlij wartość itemId
.
Teraz zaktualizuj model bazy danych, aby przechowywał on także wartość maxPoints
załącznika.
Zalecamy użycie wartości maxPoints
z odpowiedzi CREATE
:
Python
Najpierw dodaj 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óconą w odpowiedzi.
new_attachment = Attachment(
# The new attachment's unique ID, returned in the CREATE response.
attachment_id=resp.get("id"),
image_filename=key,
image_caption=value,
# Store the maxPoints value returned in the response.
max_points=int(resp.get("maxPoints")))
db.session.add(new_attachment)
db.session.commit()
Załącznik ma teraz maksymalną ocenę. Możesz teraz przetestować to działanie. Dodaj załącznik do nowego projektu i zobacz, że karta załącznika wyświetla etykietę „Synchronizacja oceny”, a wartość „Punkty” projektu ulega zmianie.
Ustawianie oceny za projekt ucznia w Classroom
W tej sekcji opisano ustawianie licznika do oceny załącznika, czyli wyniku pojedynczego ucznia dotyczącego załącznika. 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 Twoim dodatkiem, np. wczytywanie iframe widoku nauczyciela zamiast 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 danego języka:
Python
Znajdź deklarację SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS
u góry pliku webapp/attachment_routes.py
. Aby przekazać oceny za pomocą danych logowania nauczyciela, ustaw tę wartość na True
. Ustaw tę wartość na False
, aby zwracać oceny za pomocą zapisanych danych logowania, gdy uczeń prześle aktywność.
Wyznaczanie ocen za pomocą danych logowania zalogowanego nauczyciela
Aby wysłać żądanie ustawienia pointsEarned
, użyj danych logowania zalogowanego użytkownika.
Powinna być dość intuicyjna, ponieważ odzwierciedla pozostałe do tej pory wdrożone rozwiązania i nie wymaga wiele wysiłku.
Pamiętaj jednak, że nauczyciel tylko może wchodzić w interakcje z pracą ucznia w ramce iframe Sprawdzanie prac uczniów. Ma to kilka ważnych konsekwencji:
- Oceny nie są wypełniane w Classroom, dopóki nauczyciel nie podejmie działania w interfejsie Classroom.
- Aby wypełnić wszystkie oceny uczniów, nauczyciel może musieć otworzyć wszystkie przesłane przez nich treści.
- Po otrzymaniu oceny przez Classroom może minąć chwila, zanim pojawi się ona w interfejsie Classroom. Opóźnienie wynosi zazwyczaj 5–10 sekund, ale może sięgać nawet 30 sekund.
Ze względu na te czynniki nauczyciele mogą musieć wykonać znaczną ilość czasochłonnej pracy ręcznej, aby w pełni wypełnić dziennik ocen.
Aby wdrożyć to podejście, dodaj jedno dodatkowe wywołanie interfejsu API do istniejącej ścieżki sprawdzenia pracy ucznia.
Po pobraniu rekordów przesłanych przez ucznia zadań i załączników oceń zadanie ucznia i zapisz wynikową ocenę. Ustaw ocenę w polu pointsEarned
obiektu AddOnAttachmentStudentSubmission
. Na koniec prześlij żądanie PATCH
do punktu końcowego courses.courseWork.addOnAttachments.studentSubmissions
z wystąpieniem AddOnAttachmentStudentSubmission
w treści żądania. Pamiętaj, że 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()
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 działań przez nauczycieli 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 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:
- Modyfikowanie rekordów bazy danych użytkownika w celu przechowywania tokena dostępu.
- Zmień rekordy bazy danych załączników, aby przechowywać identyfikator nauczyciela.
- Pobierz dane logowania nauczyciela i (opcjonalnie) utwórz nową instancję usługi Classroom.
- Ustaw ocenę przesłanego projektu.
W ramach tej prezentacji ustaw ocenę, gdy uczeń ukończy zadanie, czyli prześle formularz w widoku ucznia.
Modyfikowanie rekordów bazy danych użytkownika w celu przechowywania tokena dostępu
Aby wykonywać wywołania interfejsu API, musisz mieć 2 unikalne tokeny: token odśwież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 jako inna osoba niż zalogowany użytkownik. Oznacza to, że proces uwierzytelniania jest niedostępny. Dlatego musisz przechowywać token dostępu wraz z tokenem odświeżania. Zaktualizuj schemat tabeli User
, tak aby zawierał 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ę 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 uwierzytelniających nauczyciela do identyfikatorów kursów. Pamiętaj jednak, że ten sam nauczyciel nie zawsze może być powiązany z konkretnym kursem.
- Wyślij
GET
żądania do punktu końcowego interfejsu Classroom APIcourses
, aby pobrać obecnych nauczycieli. Następnie przeprowadź zapytanie o lokalne rekordy użytkowników, aby znaleźć pasujące dane logowania nauczyciela. - Podczas tworzenia załącznika dodatku zapisz identyfikator nauczyciela w lokalnej bazie danych załączników. Następnie pobierz dane logowania nauczyciela z elementu
attachmentId
przekazywanego do elementu iframe widoku ucznia.
W tym przykładzie pokazujemy ostatnią opcję, ponieważ oceny są przyznawane, gdy uczeń ukończy załącznik do aktywności.
Dodaj pole identyfikatora nauczyciela do tabeli Attachment
w bazie danych:
Python
W naszym przykładzie znajduje się on w pliku webapp/models.py
.
# Database model to represent an attachment.
class Attachment(db.Model):
# The attachmentId is the unique identifier for the attachment.
attachment_id = db.Column(db.String(120), primary_key=True)
# The image filename to store.
image_filename = db.Column(db.String(120))
# The image caption to store.
image_caption = db.Column(db.String(120))
# The maximum number of points for this activity.
max_points = db.Column(db.Integer)
# The ID of the teacher that created the attachment.
teacher_id = db.Column(db.String(120))
Następnie zaktualizuj kod, który tworzy lub aktualizuje rekord Attachment
, aby przechowywał też identyfikator twórcy:
Python
W naszym przykładzie znajduje się ona w metodzie create_attachments
w pliku webapp/attachment_routes.py
.
# Store the attachment by id.
new_attachment = Attachment(
# The new attachment's unique ID, returned in the CREATE response.
attachment_id=resp.get("id"),
image_filename=key,
image_caption=value,
max_points=int(resp.get("maxPoints")),
teacher_id=flask.session["login_hint"])
db.session.add(new_attachment)
db.session.commit()
Pobieranie danych logowania nauczyciela
Znajdź trasę, która wyświetla element iframe widoku ucznia. Natychmiast po zapisaniu odpowiedzi ucznia w lokalnej bazie danych pobierz dane logowania nauczyciela z lokalnej pamięci masowej. 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 zadania
Dalsza procedura jest identyczna jak w przypadku używania danych logowania 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 przypadku poprzedniego przewodnika, jako nauczyciel utwórz projekt z załącznikiem aktywności, prześlij odpowiedź jako uczeń, a następnie otwórz zadanie w elemencie iframe z oceną zadań ucznia. Ocena powinna być widoczna w różnych momentach w zależności od podejścia do implementacji:
- Jeśli zdecydujesz się przekazać ocenę po ukończeniu zadania przez ucznia, jego ocena 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 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.