Nilai lampiran dan passback nilai

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 bernilai. Anda juga dapat mengirimkan kembali nilai ke Google Classroom secara terprogram, yang muncul di buku nilai pengajar sebagai nilai draf.

Panduan ini sedikit berbeda dari panduan lain dalam seri ini karena ada dua kemungkinan pendekatan untuk mengirimkan kembali nilai ke Classroom. Keduanya memiliki dampak yang berbeda pada pengalaman developer dan pengguna; pertimbangkan keduanya saat Anda mendesain add-on Classroom. Baca halaman panduan Berinteraksi dengan lampiran kami untuk pembahasan tambahan tentang opsi penerapan.

Perhatikan bahwa fitur penilaian di API bersifat opsional. Lampiran ini dapat digunakan dengan lampiran jenis aktivitas apa pun.

Selama penelusuran ini, Anda akan menyelesaikan hal berikut:

  • Ubah permintaan pembuatan lampiran sebelumnya ke Classroom API untuk juga menetapkan penyebut nilai lampiran.
  • Memberi skor tugas siswa secara terprogram dan menetapkan 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. Momen tepat saat hal ini terjadi bergantung pada pendekatan penerapan.

Untuk tujuan contoh ini, gunakan kembali aktivitas dari panduan sebelumnya, yang menampilkan gambar landmark terkenal kepada siswa dan meminta siswa untuk memasukkan namanya. Beri nilai penuh untuk lampiran jika siswa memasukkan nama yang benar, dan nol jika tidak.

Memahami fitur penilaian API add-on Classroom

Add-on Anda dapat menetapkan pembilang nilai dan penyebut nilai untuk lampiran. Nilai ini masing-masing ditetapkan menggunakan nilai pointsEarned dan maxPoints di API. Kartu lampiran di UI Classroom menampilkan nilai maxPoints jika telah ditetapkan.

Contoh beberapa lampiran dengan maxPoints pada satu tugas

Gambar 1. UI pembuatan tugas dengan tiga kartu lampiran add-on yang telah ditetapkan maxPoints.

Classroom Add-on API 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 dari lampiran yang memiliki label Sinkronisasi nilai di kartu lampirannya. Saat lampiran "Sinkronisasi nilai" ditetapkan pointsEarned untuk kiriman siswa, lampiran tersebut juga menetapkan nilai draf siswa untuk tugas.

Biasanya, lampiran pertama yang ditambahkan ke tugas yang menetapkan maxPoints menerima label "Sinkronisasi nilai". Lihat contoh UI pembuatan tugas yang ditampilkan dalam Gambar 1 untuk melihat contoh label "Sinkronisasi nilai". Perhatikan bahwa kartu "Lampiran 1" memiliki label "Sinkronisasi nilai" dan nilai tugas dalam 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". Hal ini merupakan batasan utama penerapan saat ini: hanya satu lampiran yang dapat memiliki label "Sinkronisasi nilai".

Jika ada beberapa lampiran yang telah menyetel maxPoints, menghapus lampiran dengan "Sinkronisasi nilai" tidak akan mengaktifkan "Sinkronisasi nilai" pada lampiran yang tersisa. Menambahkan lampiran lain yang menetapkan maxPoints akan mengaktifkan Sinkronisasi Nilai pada lampiran baru, dan nilai tugas maksimum akan disesuaikan agar cocok. 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 cara menetapkan penyebut untuk nilai lampiran; yaitu, skor maksimum yang dapat dicapai semua siswa untuk kiriman mereka. Untuk melakukannya, tetapkan nilai maxPoints lampiran.

Hanya diperlukan sedikit modifikasi pada penerapan yang ada untuk mengaktifkan fitur penilaian. Saat membuat lampiran, tambahkan nilai maxPoints di 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 ditetapkan dengan benar. Tetapkan maxPoints ke 50 sebagai demonstrasi:

Python

Tambahkan kolom maxPoints saat membuat objek attachment, tepat sebelum mengeluarkan permintaan CREATE ke courses.courseWork.addOnAttachments endpoint. 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 di database Lampiran lokal; hal ini akan menghemat panggilan API tambahan nanti saat menilai kiriman siswa. Namun, perlu diperhatikan bahwa guru dapat mengubah setelan nilai tugas secara terpisah dari add-on Anda. Kirim permintaan GET ke endpoint courses.courseWork untuk melihat nilai maxPoints tingkat tugas. Saat melakukannya, teruskan itemId di kolom CourseWork.id.

Sekarang, perbarui model database Anda untuk 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 kami berikan.

# 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 nilai "Poin" tugas berubah.

Menetapkan nilai kiriman siswa di Classroom

Bagian ini menjelaskan cara menetapkan pembilang untuk nilai lampiran; yaitu, skor siswa perorangan untuk lampiran. Untuk melakukannya, tetapkan nilai pointsEarned pengiriman lampiran siswa.

Sekarang Anda harus membuat keputusan penting: bagaimana add-on Anda harus mengeluarkan permintaan untuk menyetel pointsEarned?

Masalahnya adalah setelan pointsEarned memerlukan teacher cakupan OAuth. Anda tidak boleh memberikan 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 untuk cara menyetel pointsEarned:

  • Menggunakan kredensial pengajar yang login.
  • Menggunakan kredensial pengajar tersimpan (offline).

Bagian berikut membahas kelebihan dan kekurangan 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 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. Tetapkan nilai ini ke True untuk mengirimkan kembali nilai menggunakan kredensial pengajar yang login. Tetapkan nilai ini ke False untuk meneruskan kembali nilai menggunakan kredensial tersimpan saat siswa mengirimkan aktivitas.

Menetapkan nilai menggunakan kredensial pengajar yang login

Gunakan kredensial pengguna yang login untuk mengeluarkan permintaan guna menetapkan pointsEarned. Hal ini akan tampak cukup intuitif karena mencerminkan implementasi lainnya sejauh ini, dan hanya memerlukan sedikit upaya untuk diwujudkan.

Namun, perlu dipertimbangkan bahwa pengajar hanya berinteraksi dengan kiriman siswa di iframe Peninjauan Tugas Siswa. Hal ini memiliki beberapa implikasi penting:

  • Nilai tidak akan muncul di Classroom hingga pengajar mengambil tindakan di UI Classroom.
  • Pengajar mungkin harus membuka setiap kiriman siswa untuk mengisi semua nilai siswa.
  • Ada sedikit penundaan antara penerimaan nilai oleh Classroom dan kemunculannya di UI Classroom. Penundaan biasanya berlangsung selama lima hingga sepuluh detik, tetapi dapat berlangsung hingga 30 detik.

Kombinasi faktor-faktor ini berarti pengajar mungkin harus melakukan pekerjaan manual yang cukup banyak dan memakan waktu untuk mengisi nilai kelas secara lengkap.

Untuk menerapkan pendekatan ini, tambahkan satu panggilan API tambahan ke rute Peninjauan Tugas Siswa yang ada.

Setelah mengambil catatan lampiran dan kiriman siswa, evaluasi kiriman siswa dan simpan nilai yang dihasilkan. Tetapkan nilai dalam kolom pointsEarned objek AddOnAttachmentStudentSubmission. Terakhir, kirim permintaan PATCH ke endpoint courses.courseWork.addOnAttachments.studentSubmissions dengan instance AddOnAttachmentStudentSubmission dalam isi permintaan. Perhatikan bahwa kita juga perlu menentukan pointsEarned di 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 tersimpan untuk pengajar yang membuat lampiran. Implementasi ini mengharuskan Anda membuat kredensial menggunakan token akses dan token refresh pengajar yang sebelumnya diberi otorisasi, 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, pengguna akhir menganggap pengalaman pemberian nilai sebagai lancar dan efisien. Selain itu, pendekatan ini memungkinkan Anda memilih waktu untuk mengirimkan kembali nilai, seperti saat siswa menyelesaikan aktivitas atau secara asinkron.

Selesaikan tugas berikut untuk menerapkan pendekatan ini:

  1. Ubah rekaman database Pengguna untuk menyimpan token akses.
  2. Ubah rekaman database Lampiran untuk menyimpan ID pengajar.
  3. Ambil kredensial pengajar dan (opsional) buat instance layanan Classroom baru.
  4. Menetapkan nilai kiriman.

Untuk tujuan demonstrasi ini, tetapkan nilai saat siswa menyelesaikan aktivitas; yaitu, saat siswa mengirimkan formulir di rute Tampilan Siswa.

Ubah catatan database Pengguna untuk menyimpan token akses

Dua token unik diperlukan untuk melakukan panggilan API, yaitu token refresh dan token akses. Jika Anda telah mengikuti seri panduan ini, skema tabel User Anda seharusnya sudah menyimpan token refresh. Menyimpan token refresh sudah cukup jika Anda hanya melakukan panggilan API dengan pengguna yang login, karena Anda menerima token akses sebagai bagian dari alur autentikasi.

Namun, Anda sekarang perlu melakukan panggilan sebagai orang lain selain pengguna yang login, yang berarti alur autentikasi tidak tersedia. Oleh karena itu, Anda perlu menyimpan token akses bersama dengan token refresh. Perbarui skema tabel User untuk menyertakan token akses:

Python

Dalam contoh yang kami berikan, hal 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 rekaman User untuk juga menyimpan token akses:

Python

Dalam contoh yang kami berikan, hal 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()

Ubah catatan 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.
  • Kirim permintaan GET ke endpoint courses Classroom API untuk mendapatkan pengajar saat ini. Kemudian, kueri data pengguna lokal untuk menemukan kredensial pengajar yang cocok.
  • Saat membuat lampiran add-on, simpan ID pengajar di 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 kami berikan, hal 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 rekaman Attachment untuk juga menyimpan ID pembuat:

Python

Dalam contoh yang kami berikan, hal ini ada di metode create_attachments dalam 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 melayani iframe Tampilan Siswa. Segera setelah menyimpan respons siswa di database lokal Anda, ambil kredensial pengajar dari penyimpanan lokal Anda. Tindakan ini seharusnya mudah dilakukan mengingat persiapan pada dua langkah sebelumnya. Anda juga dapat menggunakan ini untuk membuat instance baru layanan Classroom bagi pengguna pengajar:

Python

Dalam contoh yang kami berikan, hal ini ada dalam 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,
    credentials=teacher_credentials)

Menetapkan nilai kiriman

Prosedur dari sini identik dengan penggunaan kredensial pengajar yang login. Namun, perhatikan bahwa Anda harus melakukan panggilan dengan kredensial pengajar yang diambil pada 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

Mirip dengan panduan sebelumnya, buat tugas dengan lampiran jenis aktivitas sebagai pengajar, kirim respons sebagai siswa, lalu buka kiriman siswa di iframe Peninjauan Tugas Siswa. Anda akan dapat melihat nilai muncul pada waktu yang berbeda, bergantung pada pendekatan penerapan Anda:

  • Jika Anda memilih untuk mengirimkan kembali nilai saat siswa menyelesaikan aktivitas, Anda akan melihat draf nilai mereka di UI sebelum membuka iframe Peninjauan Tugas Siswa. Anda juga dapat melihatnya di daftar siswa saat membuka tugas, dan di kotak "Nilai" di samping iframe Peninjauan Tugas Siswa.
  • Jika Anda memilih untuk mengirimkan kembali nilai saat pengajar membuka iframe Peninjauan 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 melanjutkan ke langkah berikutnya: membuat lampiran di luar Google Classroom.