Il s'agit de la sixième procédure de la série de tutoriels sur les modules complémentaires Classroom.
Dans ce tutoriel, vous allez modifier l'exemple de l'étape précédente pour créer une pièce jointe de type activité notée. Vous pouvez également transmettre une note à Google Classroom de manière programmatique. Elle apparaît alors dans le carnet de notes de l'enseignant en tant que note brouillon.
Cette procédure diffère légèrement des autres de la série, car il existe deux approches possibles pour renvoyer les notes dans Classroom. Les deux ont un impact distinct sur l'expérience du développeur et de l'utilisateur. Tenez-en compte lorsque vous concevez votre module complémentaire Classroom. Pour en savoir plus sur les options d'implémentation, consultez notre guide d'interaction avec les pièces jointes.
Notez que les fonctionnalités de notation de l'API sont facultatives. Ils peuvent être utilisés avec n'importe quel fichier joint de type activité.
Au cours de ce tutoriel, vous allez effectuer les opérations suivantes:
- Modifiez les requêtes de création d'annexe précédentes envoyées à l'API Classroom pour définir également le dénominateur de la note de l'annexe.
- Évaluez de manière programmatique le devoir de l'élève et définissez le numérateur de la note de l'annexe.
- Implémentez deux approches pour transmettre la note de la soumission à Classroom à l'aide d'identifiants d'enseignant connectés ou hors connexion.
Une fois le devoir terminé, les notes s'affichent dans le cahier de notes Classroom une fois le comportement de passback déclenché. Le moment exact où cela se produit dépend de l'approche d'implémentation.
Pour les besoins de cet exemple, réutilisez l'activité de la procédure guidée précédente, dans laquelle un élève voit une image d'un monument célèbre et est invité à saisir son nom. Attribuez la note maximale à la pièce jointe si l'élève saisit le nom correct, sinon attribuez-lui zéro.
Comprendre la fonctionnalité de notation de l'API des modules complémentaires Classroom
Votre module complémentaire peut définir à la fois le numérateur et le dénominateur de la note pour une pièce jointe. Ils sont respectivement définis à l'aide des valeurs pointsEarned
et maxPoints
dans l'API. Une fiche d'attachement dans l'interface utilisateur de Classroom affiche la valeur maxPoints
lorsqu'elle a été définie.
Figure 1. Interface utilisateur de création de devoir avec trois fiches de pièces jointes de module complémentaire pour lesquelles maxPoints
est défini.
L'API des modules complémentaires Classroom vous permet de configurer les paramètres des notes de pièces jointes et de définir le score obtenu. Il ne s'agit pas des notes de l'exercice. Toutefois, les paramètres de note du devoir suivent les paramètres de note de l'annexe associée au libellé Synchronisation des notes sur sa fiche. Lorsque la pièce jointe "Synchronisation des notes" définit pointsEarned
pour un devoir envoyé par un élève, elle définit également la note provisoire de l'élève pour le devoir.
En règle générale, la première pièce jointe ajoutée au devoir qui définit maxPoints
reçoit le libellé "Synchronisation des notes". Pour voir un exemple du libellé "Synchronisation des notes", consultez l'UI de création de devoir illustrée dans la figure 1. Notez que la fiche "Pièce jointe 1" porte le libellé "Synchronisation des notes" et que la note de l'exercice dans le cadre rouge a été mise à jour à 50 points. Notez également que, bien que la figure 1 montre trois fiches d'attachement, une seule porte le libellé "Synchronisation des notes". Il s'agit d'une limite clé de l'implémentation actuelle: une seule pièce jointe peut porter l'étiquette "Synchronisation des notes".
Si plusieurs pièces jointes ont défini maxPoints
, la suppression de la pièce jointe avec "Synchronisation des notes" n'active pas la synchronisation des notes sur les autres pièces jointes. L'ajout d'une autre pièce jointe qui définit maxPoints
active la synchronisation des notes sur la nouvelle pièce jointe, et la note maximale de l'exercice s'ajuste en conséquence. Il n'existe aucun mécanisme permettant de voir de manière programmatique quelle pièce jointe est associée au libellé "Synchronisation des notes" ni de savoir combien de pièces jointes comporte un devoir particulier.
Définir la note maximale d'une pièce jointe
Cette section décrit comment définir le dénominateur d'une note d'un fichier joint, c'est-à-dire le score maximal que tous les élèves peuvent obtenir pour leurs envois. Pour ce faire, définissez la valeur maxPoints
de la pièce jointe.
Seule une modification mineure de notre implémentation existante est nécessaire pour activer les fonctionnalités de notation. Lorsque vous créez une pièce jointe, ajoutez la valeur maxPoints
dans le même objet AddOnAttachment
qui contient les champs studentWorkReviewUri
, teacherViewUri
et d'autres champs de pièce jointe.
Notez que la note maximale par défaut d'une nouvelle devoir est de 100. Nous vous suggérons de définir maxPoints
sur une valeur autre que 100 afin de vérifier que les notes sont définies correctement. Pour illustration, définissez maxPoints
sur 50:
Python
Ajoutez le champ maxPoints
lors de la création de l'objet attachment
, juste avant d'envoyer une requête CREATE
au point de terminaison courses.courseWork.addOnAttachments
. Vous le trouverez dans le fichier webapp/attachment_routes.py
si vous suivez l'exemple fourni.
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}",
}
Pour les besoins de cette démonstration, vous stockez également la valeur maxPoints
dans votre base de données d'attachements locale. Vous évitez ainsi d'avoir à effectuer un appel d'API supplémentaire plus tard lorsque vous notez les devoirs des élèves. Notez toutefois qu'il est possible que les enseignants modifient les paramètres de notation des devoirs indépendamment de votre module complémentaire. Envoyez une requête GET
au point de terminaison courses.courseWork
pour afficher la valeur maxPoints
au niveau de l'affectation. Dans ce cas, transmettez itemId
dans le champ CourseWork.id
.
Modifiez maintenant votre modèle de base de données pour qu'il contienne également la valeur maxPoints
de la pièce jointe.
Nous vous recommandons d'utiliser la valeur maxPoints
de la réponse CREATE
:
Python
Commencez par ajouter un champ max_points
au tableau Attachment
. Vous le trouverez dans le fichier webapp/models.py
si vous suivez notre exemple.
# 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)
Revenez à la requête CREATE
courses.courseWork.addOnAttachments
. Stockez la valeur maxPoints
renvoyée dans la réponse.
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()
La note maximale de l'élément joint est désormais définie. Vous devriez pouvoir tester ce comportement maintenant. Ajoutez une pièce jointe à un devoir et observez que la fiche de pièce jointe affiche le libellé "Synchronisation des notes" et que la valeur "Points" du devoir change.
Attribuer une note à un devoir dans Classroom
Cette section explique comment définir le dénominateur d'une note d'une pièce jointe, c'est-à-dire le score d'un élève individuel pour la pièce jointe. Pour ce faire, définissez la valeur pointsEarned
d'un devoir d'élève.
Vous devez maintenant prendre une décision importante: comment votre module complémentaire doit-il envoyer une requête pour définir pointsEarned
?
Le problème est que le paramètre pointsEarned
nécessite le champ d'application OAuth teacher
.
Vous ne devez pas accorder d'étendue teacher
aux utilisateurs élèves. Cela pourrait entraîner un comportement inattendu lorsque les élèves interagissent avec votre module complémentaire, par exemple en chargeant l'iframe de la vue enseignant au lieu de l'iframe de la vue élève. Vous avez donc deux options pour définir pointsEarned
:
- Utiliser les identifiants de l'enseignant connecté.
- Utiliser des identifiants d'enseignant stockés (hors connexion)
Les sections suivantes décrivent les compromis de chaque approche avant de présenter chaque implémentation. Notez que nos exemples illustrent les deux approches permettant de transmettre une note à Classroom. Consultez les instructions spécifiques à la langue ci-dessous pour savoir comment sélectionner une approche lorsque vous exécutez les exemples fournis:
Python
Recherchez la déclaration SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS
en haut du fichier webapp/attachment_routes.py
. Définissez cette valeur sur True
pour transmettre les notes à l'aide des identifiants de l'enseignant connecté. Définissez cette valeur sur False
pour renvoyer les notes à l'aide des identifiants stockés lorsque l'élève soumet l'activité.
Définir des notes à l'aide des identifiants de l'enseignant connecté
Utilisez les identifiants de l'utilisateur connecté pour envoyer la requête de définition de pointsEarned
.
Cela devrait sembler assez intuitif, car il reflète le reste de l'implémentation jusqu'à présent et ne nécessite que peu d'efforts pour être réalisé.
Toutefois, notez que l'enseignant n'interagit qu'avec le devoir de l'élève dans l'iframe d'évaluation des devoirs des élèves. Cela présente des conséquences importantes:
- Aucune note n'est renseignée dans Classroom tant que l'enseignant n'a pas effectué d'action dans l'interface utilisateur de Classroom.
- Un enseignant peut être amené à ouvrir chaque devoir remis par un élève pour renseigner toutes les notes.
- Un bref délai s'écoule entre la réception de la note par Classroom et son affichage dans l'interface utilisateur de Classroom. Le délai est généralement de cinq à dix secondes, mais peut aller jusqu'à 30 secondes.
La combinaison de ces facteurs signifie que les enseignants peuvent devoir effectuer un travail manuel considérable et long pour renseigner complètement les notes d'un cours.
Pour implémenter cette approche, ajoutez un appel d'API à votre parcours d'évaluation des devoirs des élèves existant.
Après avoir récupéré les enregistrements de devoir et de pièce jointe de l'élève, évaluez le devoir de l'élève et stockez la note obtenue. Définissez la note dans le champ pointsEarned
d'un objet AddOnAttachmentStudentSubmission
. Enfin, envoyez une requête PATCH
au point de terminaison courses.courseWork.addOnAttachments.studentSubmissions
avec l'instance AddOnAttachmentStudentSubmission
dans le corps de la requête. Notez que nous devons également spécifier pointsEarned
dans updateMask
dans notre requête 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()
Définir des notes à l'aide des identifiants d'enseignant hors connexion
La deuxième approche pour définir des notes nécessite l'utilisation d'identifiants stockés pour l'enseignant qui a créé la pièce jointe. Cette implémentation nécessite de créer des identifiants à l'aide des jetons d'actualisation et d'accès d'un enseignant précédemment autorisé, puis d'utiliser ces identifiants pour définir pointsEarned
.
L'un des avantages clés de cette approche est que les notes sont renseignées sans que l'enseignant n'ait à intervenir dans l'interface utilisateur de Classroom, ce qui évite les problèmes mentionnés ci-dessus. Résultat : les utilisateurs finaux perçoivent l'expérience de notation comme fluide et efficace. De plus, cette approche vous permet de choisir le moment où vous renvoyez les notes, par exemple lorsque les élèves terminent l'activité ou de manière asynchrone.
Pour mettre en œuvre cette approche, effectuez les tâches suivantes:
- Modifiez les enregistrements de la base de données utilisateur pour stocker un jeton d'accès.
- Modifier les enregistrements de la base de données des pièces jointes pour stocker un ID d'enseignant
- Récupérez les identifiants de l'enseignant et (facultatif) créez une nouvelle instance de service Classroom.
- Définissez la note d'un devoir.
Pour les besoins de cette démonstration, définissez la note lorsque l'élève termine l'activité, c'est-à-dire lorsqu'il envoie le formulaire dans le parcours "Vue de l'élève".
Modifier les enregistrements de la base de données utilisateur pour stocker le jeton d'accès
Deux jetons uniques sont nécessaires pour effectuer des appels d'API : le jeton d'actualisation et le jeton d'accès. Si vous avez suivi la série de tutoriels jusqu'à présent, votre schéma de table User
devrait déjà stocker un jeton d'actualisation. Le stockage du jeton d'actualisation est suffisant lorsque vous n'effectuez que des appels d'API avec l'utilisateur connecté, car vous recevez un jeton d'accès dans le cadre du flux d'authentification.
Toutefois, vous devez désormais passer des appels en tant qu'utilisateur autre que celui connecté. Par conséquent, le flux d'authentification n'est pas disponible. Vous devez donc stocker le jeton d'accès avec le jeton d'actualisation. Mettez à jour le schéma de votre table User
pour inclure un jeton d'accès:
Python
Dans l'exemple fourni, il s'agit du fichier 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())
Ensuite, mettez à jour tout code qui crée ou met à jour un enregistrement User
pour stocker également le jeton d'accès:
Python
Dans l'exemple fourni, il s'agit du fichier 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()
Modifier les enregistrements de la base de données des pièces jointes pour stocker un ID d'enseignant
Pour définir une note pour une activité, appelez pointsEarned
en tant que professeur du cours. Pour ce faire, vous pouvez procéder de plusieurs manières:
- Stockez une mise en correspondance locale des identifiants des enseignants avec les ID de cours. Notez toutefois que le même enseignant n'est pas toujours associé à un cours donné.
- Envoyez des requêtes
GET
au point de terminaisoncourses
de l'API Classroom pour obtenir le ou les enseignants actuels. Interrogez ensuite les enregistrements utilisateur locaux pour trouver les identifiants d'enseignant correspondants. - Lorsque vous créez une pièce jointe de module complémentaire, stockez un ID d'enseignant dans la base de données de pièces jointes locales. Ensuite, récupérez les identifiants de l'enseignant à partir de la
attachmentId
transmise à l'iframe de la vue de l'élève.
Cet exemple illustre la dernière option, car vous définissez des notes lorsque l'élève termine une pièce jointe d'activité.
Ajoutez un champ d'identifiant d'enseignant à la table Attachment
de votre base de données:
Python
Dans l'exemple fourni, il s'agit du fichier 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))
Modifiez ensuite tout code qui crée ou met à jour un enregistrement Attachment
pour stocker également l'ID du créateur:
Python
Dans l'exemple fourni, il s'agit de la méthode create_attachments
du fichier 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()
Récupérez les identifiants de l'enseignant
Recherchez l'itinéraire qui diffuse l'iFrame de la vue élève. Immédiatement après avoir stocké la réponse de l'élève dans votre base de données locale, récupérez les identifiants de l'enseignant à partir de votre stockage local. Cette opération devrait être simple compte tenu de la préparation effectuée au cours des deux étapes précédentes. Vous pouvez également les utiliser pour créer une instance du service Classroom pour l'utilisateur enseignant:
Python
Dans l'exemple fourni, il s'agit de la méthode load_activity_attachment
du fichier 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)
Définir la note d'un devoir
La procédure à partir de là est identique à celle d'utiliser les identifiants de l'enseignant connecté. Notez toutefois que vous devez passer l'appel avec les identifiants de l'enseignant récupérés à l'étape précédente:
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()
Tester le module complémentaire
Comme dans la procédure précédente, créez un devoir avec une pièce jointe de type activité en tant qu'enseignant, envoyez une réponse en tant qu'élève, puis ouvrez le devoir dans l'iframe "Examen du travail des élèves". La note devrait s'afficher à différents moments en fonction de votre approche d'implémentation:
- Si vous avez choisi de communiquer une note à l'élève lorsqu'il a terminé l'activité, vous devriez déjà voir sa note provisoire dans l'interface utilisateur avant d'ouvrir l'iframe d'évaluation du travail des élèves. Vous pouvez également le voir dans la liste des élèves lorsque vous ouvrez le devoir, ainsi que dans la zone "Note" à côté de l'iframe d'évaluation du travail des élèves.
- Si vous avez choisi de transmettre une note lorsque l'enseignant ouvre l'iframe d'évaluation des travaux des élèves, la note doit apparaître dans le champ "Note" peu de temps après le chargement de l'iframe. Comme indiqué ci-dessus, cette opération peut prendre jusqu'à 30 secondes. La note de l'élève concerné devrait ensuite également apparaître dans les autres vues du carnet de notes Classroom.
Vérifiez que le score correct s'affiche pour l'élève.
Félicitations ! Vous pouvez maintenant passer à l'étape suivante: créer des pièces jointes en dehors de Google Classroom.