Ini adalah panduan keenam dalam seri panduan add-on Classroom.
Dalam panduan ini, Anda akan mengubah contoh dari langkah panduan sebelumnya untuk menghasilkan lampiran jenis aktivitas yang dinilai. Anda juga meneruskan nilai kembali ke Google Classroom secara terprogram, yang muncul di buku nilai pengajar sebagai draf nilai.
Panduan ini sedikit berbeda dari yang lain dalam seri ini karena ada dua kemungkinan pendekatan untuk meneruskan nilai kembali ke Classroom. Keduanya memiliki dampak yang berbeda terhadap pengalaman developer dan pengguna; pertimbangkan keduanya saat mendesain add-on Classroom. Baca Halaman panduan berinteraksi dengan lampiran untuk diskusi tambahan tentang opsi penerapan.
Perhatikan bahwa fitur penilaian di API bersifat opsional. Class ini dapat digunakan dengan lampiran jenis aktivitas apa pun.
Dalam panduan ini, Anda akan menyelesaikan langkah-langkah berikut:
- Ubah permintaan pembuatan lampiran sebelumnya ke Classroom API untuk menetapkan penyebut nilai lampiran juga.
- Secara terprogram beri nilai tugas siswa dan tetapkan pembilang nilai lampiran.
- Terapkan dua pendekatan untuk meneruskan nilai kiriman ke Classroom menggunakan kredensial pengajar yang login atau offline.
Setelah selesai, nilai akan muncul di buku nilai Classroom setelah perilaku passback dipicu. Waktu persis terjadinya hal ini bergantung pada pendekatan implementasi.
Untuk tujuan contoh ini, gunakan kembali aktivitas dari panduan sebelumnya, yang menampilkan gambar bangunan terkenal kepada siswa dan diminta untuk memasukkan namanya. Tetapkan nilai penuh untuk lampiran jika siswa memasukkan nama yang benar, nol jika tidak.
Memahami fitur penilaian API add-on Classroom
Add-on Anda dapat menyetel pembilang nilai dan penyebut nilai untuk
lampiran. Keduanya ditetapkan menggunakan nilai pointsEarned
dan maxPoints
di API. Kartu lampiran di UI Classroom menampilkan
nilai maxPoints
saat nilai ditetapkan.
Gambar 1. UI pembuatan tugas dengan tiga kartu lampiran add-on yang
telah menetapkan maxPoints
.
Add-on API Classroom memungkinkan Anda mengonfigurasi setelan dan menetapkan
skor yang diperoleh untuk nilai lampiran. Nilai ini tidak sama dengan
nilai tugas. Namun, setelan nilai tugas mengikuti
setelan nilai lampiran lampiran yang memiliki label Sinkronisasi nilai di
kartu lampirannya. Jika lampiran "Sinkronisasi nilai" menyetel pointsEarned
untuk
pengiriman siswa, lampiran juga akan menetapkan draf nilai siswa untuk tugas tersebut.
Biasanya, lampiran pertama yang ditambahkan ke tugas yang menetapkan
maxPoints
akan menerima label "Sinkronisasi nilai". Lihat contoh UI pembuatan tugas
yang ditunjukkan pada Gambar 1 untuk contoh label "Sinkronisasi nilai". Perhatikan bahwa
kartu "Lampiran 1" memiliki label "Sinkronisasi nilai" dan nilai tugas
di kotak merah telah diperbarui menjadi 50 poin. Perhatikan juga bahwa meskipun Gambar 1
menampilkan tiga kartu lampiran, hanya satu kartu yang memiliki label "Sinkronisasi nilai". Ini adalah
batasan utama implementasi saat ini: hanya satu lampiran yang dapat memiliki
label "Sinkronisasi nilai".
Jika ada beberapa lampiran yang telah menetapkan maxPoints
, menghapus
lampiran dengan "Sinkronisasi nilai" tidak akan mengaktifkan "Sinkronisasi nilai" pada
lampiran lainnya. Menambahkan lampiran lain yang menetapkan maxPoints
akan mengaktifkan
Sinkronisasi Nilai pada lampiran baru, dan nilai tugas maksimum akan disesuaikan
dengan kecocokan. Tidak ada mekanisme untuk melihat secara terprogram lampiran mana yang memiliki label "Sinkronisasi nilai" atau untuk melihat jumlah lampiran yang dimiliki tugas tertentu.
Menetapkan nilai maksimum lampiran
Bagian ini menjelaskan penetapan penyebut untuk nilai lampiran; yaitu, skor maksimum yang dapat dicapai semua siswa untuk
tugas kiriman mereka. Untuk melakukannya, tetapkan nilai maxPoints
lampiran.
Hanya diperlukan perubahan kecil pada implementasi yang ada untuk mengaktifkan fitur penilaian. Saat membuat lampiran, tambahkan nilai maxPoints
dalam objek AddOnAttachment
yang sama yang berisi studentWorkReviewUri
, teacherViewUri
, dan kolom lampiran lainnya.
Perhatikan bahwa skor maksimum default untuk tugas baru adalah 100. Sebaiknya
tetapkan maxPoints
ke nilai selain 100 agar Anda dapat memverifikasi bahwa
nilai sudah ditetapkan dengan benar. Setel maxPoints
ke 50 sebagai demonstrasi:
Python
Tambahkan kolom maxPoints
saat membuat objek attachment
, tepat
sebelum mengeluarkan permintaan CREATE
ke
endpoint courses.courseWork.addOnAttachments
. Anda dapat menemukannya di
file webapp/attachment_routes.py
jika mengikuti contoh yang kami berikan.
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}",
}
Untuk tujuan demonstrasi ini, Anda juga menyimpan nilai maxPoints
dalam
database Lampiran lokal sehingga Anda tidak perlu melakukan panggilan API tambahan
nanti saat menilai kiriman siswa. Namun, perlu diketahui bahwa
pengajar dapat mengubah setelan nilai tugas secara terpisah dari add-on Anda. Kirim
permintaan GET
ke endpoint courses.courseWork
untuk melihat
nilai maxPoints
level penetapan. Saat melakukannya, teruskan itemId
di kolom
CourseWork.id
.
Sekarang update model database Anda agar juga menyimpan nilai maxPoints
lampiran.
Sebaiknya gunakan nilai maxPoints
dari respons CREATE
:
Python
Pertama, tambahkan kolom max_points
ke tabel Attachment
. Anda dapat menemukannya
di file webapp/models.py
jika mengikuti contoh yang diberikan.
# 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)
Kembali ke permintaan courses.courseWork.addOnAttachments
CREATE
. Simpan
nilai maxPoints
yang ditampilkan dalam respons.
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()
Lampiran kini memiliki nilai maksimum. Anda seharusnya dapat menguji perilaku ini sekarang; tambahkan lampiran ke tugas baru, dan amati bahwa kartu lampiran menampilkan label "Sinkronisasi nilai" dan perubahan nilai "Poin" tugas.
Menetapkan nilai kiriman siswa di Classroom
Bagian ini menjelaskan setelan numerator untuk nilai lampiran, yaitu skor lampiran milik masing-masing siswa. Untuk melakukannya, tetapkan nilai
pointsEarned
kiriman siswa.
Kini Anda memiliki keputusan penting yang harus diambil: bagaimana cara add-on Anda mengajukan
permintaan untuk menetapkan pointsEarned
?
Masalahnya adalah setelan pointsEarned
memerlukan cakupan OAuth teacher
.
Sebaiknya jangan berikan cakupan teacher
kepada pengguna siswa. Hal ini dapat menyebabkan
perilaku yang tidak terduga saat siswa berinteraksi dengan add-on Anda seperti memuat
iframe Tampilan Pengajar, bukan iframe Tampilan Siswa. Oleh karena itu, Anda memiliki dua
pilihan terkait cara menetapkan pointsEarned
:
- Menggunakan kredensial pengajar yang login.
- Menggunakan kredensial pengajar yang disimpan (offline).
Bagian berikut membahas konsekuensi dari setiap pendekatan sebelum mendemonstrasikan setiap penerapan. Perhatikan bahwa contoh yang kami berikan menunjukkan kedua pendekatan untuk meneruskan nilai ke Classroom. Lihat petunjuk khusus bahasa di bawah ini untuk melihat cara memilih pendekatan saat menjalankan contoh yang diberikan:
Python
Temukan deklarasi SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS
di bagian atas
file webapp/attachment_routes.py
. Setel nilai ini ke True
untuk meneruskan
nilai menggunakan kredensial pengajar yang login. Tetapkan nilai ini ke False
untuk meneruskan nilai menggunakan kredensial yang disimpan saat siswa mengirimkan
aktivitas.
Menetapkan nilai menggunakan kredensial pengajar yang login
Gunakan kredensial pengguna yang login untuk membuat permintaan menyetel pointsEarned
.
Hal ini seharusnya terlihat cukup intuitif karena mencerminkan implementasi lainnya
sejauh ini, dan memerlukan sedikit upaya untuk menyadarinya.
Namun, perhatikan bahwa pengajar hanya berinteraksi dengan kiriman siswa di iframe Tinjauan Tugas Siswa. Hal ini memiliki beberapa implikasi penting:
- Nilai tidak akan terisi di Classroom hingga pengajar mengambil tindakan di UI Classroom.
- Pengajar mungkin harus membuka setiap kiriman siswa untuk mengisi semua nilai siswa.
- Ada penundaan singkat antara Classroom menerima nilai dan kemunculannya di UI Classroom. Penundaan ini biasanya lima hingga sepuluh detik, tetapi bisa hingga 30 detik.
Kombinasi dari faktor-faktor ini berarti pengajar mungkin harus melakukan pekerjaan manual yang memakan waktu dan banyak waktu untuk mengisi nilai kelas sepenuhnya.
Untuk menerapkan pendekatan ini, tambahkan satu panggilan API tambahan ke rute Peninjauan Tugas Siswa yang ada.
Setelah mengambil data lampiran dan kiriman siswa, evaluasi
tugas yang dikirimkan siswa dan simpan nilai yang dihasilkan. Tetapkan nilai di kolom pointsEarned
pada objek AddOnAttachmentStudentSubmission
. Terakhir,
kirimkan permintaan PATCH
ke
endpoint courses.courseWork.addOnAttachments.studentSubmissions
dengan
instance AddOnAttachmentStudentSubmission
dalam isi permintaan. Perhatikan bahwa kita
juga perlu menentukan pointsEarned
dalam updateMask
dalam permintaan 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()
Menetapkan nilai menggunakan kredensial pengajar offline
Pendekatan kedua untuk menetapkan nilai memerlukan penggunaan kredensial yang disimpan
untuk pengajar yang membuat lampiran. Implementasi ini mengharuskan
Anda membuat kredensial menggunakan token refresh dan
akses pengajar yang telah diizinkan sebelumnya, lalu menggunakan kredensial ini untuk menetapkan pointsEarned
.
Keuntungan penting dari pendekatan ini adalah nilai diisi tanpa memerlukan tindakan pengajar di UI Classroom, sehingga menghindari masalah yang disebutkan di atas. Hasilnya adalah pengguna akhir menganggap pengalaman penilaian tersebut berjalan lancar dan efisien. Selain itu, pendekatan ini memungkinkan Anda memilih momen saat Anda meneruskan nilai, seperti saat siswa menyelesaikan aktivitas atau secara asinkron.
Selesaikan tugas berikut untuk menerapkan pendekatan ini:
- Mengubah data database Pengguna untuk menyimpan token akses.
- Mengubah data database Lampiran untuk menyimpan ID pengajar.
- Ambil kredensial pengajar dan (secara opsional) buat instance layanan Classroom baru.
- Tetapkan nilai kiriman.
Untuk tujuan demonstrasi ini, tetapkan nilai saat siswa menyelesaikan aktivitas; yaitu, saat siswa mengirimkan formulir di rute Tampilan Siswa.
Mengubah data database Pengguna untuk menyimpan token akses
Dua token unik diperlukan untuk melakukan panggilan API, token refresh dan
token akses. Jika Anda telah mengikuti rangkaian panduan sejauh ini, skema tabel User
seharusnya sudah menyimpan token refresh. Menyimpan token refresh sudah cukup jika Anda hanya melakukan panggilan API dengan pengguna yang login, karena Anda akan menerima token akses sebagai bagian dari alur autentikasi.
Namun, sekarang Anda harus melakukan panggilan sebagai orang selain pengguna yang login,
yang berarti alur autentikasi tidak tersedia. Dengan demikian, Anda harus menyimpan token akses bersama token refresh. Perbarui skema tabel User
Anda untuk menyertakan token akses:
Python
Dalam contoh yang diberikan, file ini ada dalam file 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())
Kemudian, perbarui kode apa pun yang membuat atau memperbarui data User
untuk menyimpan token akses:
Python
Dalam contoh yang diberikan, file ini ada dalam file 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()
Mengubah data database Lampiran untuk menyimpan ID pengajar
Untuk menetapkan nilai aktivitas, lakukan panggilan untuk menetapkan pointsEarned
sebagai
pengajar dalam kursus. Ada beberapa cara untuk melakukannya:
- Simpan pemetaan lokal kredensial pengajar ke ID kursus. Namun, perlu diperhatikan bahwa pengajar yang sama mungkin tidak selalu dikaitkan dengan kursus tertentu.
- Berikan permintaan
GET
ke endpointcourses
Classroom API untuk mendapatkan pengajar saat ini. Kemudian, buat kueri data pengguna lokal untuk menemukan kredensial pengajar yang cocok. - Saat membuat lampiran add-on, simpan ID pengajar dalam database
lampiran lokal. Kemudian, ambil kredensial pengajar dari
attachmentId
yang diteruskan ke iframe Tampilan Siswa.
Contoh ini menunjukkan opsi terakhir karena Anda menetapkan nilai saat siswa menyelesaikan lampiran aktivitas.
Tambahkan kolom ID pengajar ke tabel Attachment
database Anda:
Python
Dalam contoh yang diberikan, file ini ada dalam file 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))
Kemudian, perbarui kode apa pun yang membuat atau memperbarui data Attachment
untuk
menyimpan ID pembuat juga:
Python
Dalam contoh yang diberikan, ini ada di metode create_attachments
di file
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()
Mengambil kredensial pengajar
Temukan rute yang menayangkan iframe Tampilan Siswa. Segera setelah menyimpan respons siswa di database lokal, ambil kredensial pengajar dari penyimpanan lokal Anda. Hal ini seharusnya mudah mengingat persiapan dalam dua langkah sebelumnya. Anda juga dapat menggunakannya untuk membuat instance baru layanan Classroom bagi pengguna pengajar:
Python
Dalam contoh yang diberikan, ini ada di metode load_activity_attachment
di file 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)
Menetapkan nilai kiriman
Prosedurnya di sini identik dengan menggunakan kredensial pengajar yang login. Namun, perhatikan bahwa Anda harus melakukan panggilan dengan kredensial pengajar yang diambil di langkah sebelumnya:
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()
Menguji add-on
Serupa dengan panduan sebelumnya, buat tugas dengan lampiran jenis aktivitas sebagai pengajar, kirim respons sebagai siswa, lalu buka kiriman mereka di iframe Tinjauan Pekerjaan Siswa. Anda akan dapat melihat nilai muncul pada waktu yang berbeda, bergantung pada pendekatan penerapan Anda:
- Jika memilih untuk meneruskan kembali nilai saat siswa menyelesaikan aktivitas, Anda akan melihat draf nilai siswa tersebut di UI sebelum membuka iframe Tinjauan Tugas Siswa. Anda juga dapat melihatnya dalam daftar siswa saat membuka tugas, dan dalam kotak "Nilai" di samping iframe Peninjauan Tugas Siswa.
- Jika Anda memilih untuk meneruskan kembali nilai saat pengajar membuka iframe Tinjauan Tugas Siswa, nilai akan muncul di kotak "Nilai" segera setelah iframe dimuat. Seperti yang disebutkan di atas, proses ini dapat memerlukan waktu hingga 30 detik. Setelah itu, nilai untuk siswa tertentu juga akan muncul di tampilan buku nilai Classroom lainnya.
Pastikan skor yang benar muncul untuk siswa.
Selamat! Anda siap untuk melanjutkan ke langkah berikutnya: membuat lampiran di luar Google Classroom.