Notes en pièce jointe et renvoi des notes

Il s'agit du sixième tutoriel des modules complémentaires Classroom cette série de tutoriels.

Dans ce tutoriel, vous allez modifier l'exemple de l'étape précédente. pour générer un rattachement de type activité noté. Vous renvoyez également une note. à Google Classroom par programmation, qui apparaît dans la note de l'enseignant en tant que note temporaire.

Ce tutoriel diffère légèrement des autres de la série, car il comporte présenter deux approches possibles pour renvoyer les notes aux Classroom. Les deux ont un impact distinct sur le développeur et l'utilisateur expériences ; tenez compte de ces deux éléments lorsque vous concevez votre module complémentaire Classroom. Consultez notre guide Interagir avec les pièces jointes pour plus de discussion sur les options d'implémentation.

Notez que les fonctionnalités de notation de l'API sont facultatives. Ils peuvent être utilisés avec les pièces jointes de type activité.

Dans ce tutoriel, vous allez:

  • Remplacez les précédentes demandes de création de pièces jointes par API Classroom pour définir également le dénominateur des notes de la pièce jointe.
  • Notez le travail de l'élève et définissez son le numérateur des notes.
  • Mettre en œuvre deux approches pour transmettre la note du devoir à Classroom à l'aide de vos identifiants d'enseignant connectés ou hors connexion

Une fois l'opération terminée, les notes s'affichent dans le carnet de notes Classroom après la le comportement de renvoi est déclenché. Le moment exact où cela se produit dépend l'approche d'implémentation.

Pour les besoins de cet exemple, réutilisez l'activité de l'activité visite guidée, au cours de laquelle un élève voit une image d'un site célèbre invité à saisir son nom. Marquez intégralement la pièce jointe si l'élève saisit le nom correct, ou zéro dans le cas contraire.

Comprendre la fonctionnalité de notation des modules complémentaires Classroom à l'aide de l'API

Votre module complémentaire peut définir à la fois le numérateur et le dénominateur des notes pour une en pièce jointe. Elles sont respectivement définies à l'aide de pointsEarned et maxPoints dans l'API. Une fiche en pièce jointe dans l'interface utilisateur de Classroom affiche la valeur maxPoints lorsqu'elle a été définie.

Exemple de plusieurs pièces jointes comportant un maximum de points max.
attribution

Figure 1. Interface utilisateur de création d'un devoir avec trois cartes de modules complémentaires avoir défini maxPoints.

L'API Classroom Add-ons vous permet de configurer les paramètres le score obtenu pour les notes associées aux pièces jointes. Ils sont différents des devoirs. Toutefois, les paramètres de note sont conformes aux les paramètres de note de la pièce jointe dont le libellé Synchronisation des notes est activé sa carte jointe. Lorsque la fonctionnalité "Synchronisation des notes" la pièce jointe pointsEarned définit une envoyé par un élève, elle définit également la note temporaire de l'élève pour le devoir.

En général, la première pièce jointe qui a été ajoutée au devoir qui définit maxPoints reçoit la notification "Synchronisation des notes" libellé. Afficher l'interface utilisateur de création d'un devoir exemple de la figure 1 pour l'utilisation de la fonction libellé. Notez que la « Pièce jointe 1 » comporte l'option "Synchronisation des notes" étiquette et que la note du devoir dans le cadre rouge est passé à 50 points. Notez également que, bien que la figure 1 affiche trois cartes pour pièces jointes, une seule contient la mention "Synchronisation des notes" libellé. C'est une limitation majeure de la mise en œuvre actuelle: un seul rattachement peut l'option "Synchronisation des notes" (étiquette).

Si maxPoints est défini pour plusieurs pièces jointes, la suppression pièce jointe contenant la mention "Synchronisation des notes" n'active pas l'option "Synchronisation des notes" sur l'un des pièces jointes restantes. L'ajout d'une autre pièce jointe qui définit maxPoints active Synchronisation des notes sur la nouvelle pièce jointe et la note maximale du devoir est ajustée correspond. Il n'existe aucun mécanisme permettant de voir de manière programmatique quelle pièce jointe est associée au "Synchronisation des notes" un libellé ni le nombre de pièces jointes associées à un devoir particulier.

Définir la note maximale d'une pièce jointe

Cette section explique comment définir le dénominateur d'une note de pièce jointe. que la note maximale que tous les élèves peuvent obtenir envois. Pour ce faire, définissez la valeur maxPoints du rattachement.

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 studentWorkReviewUri, teacherViewUri et d'autres champs associés aux pièces jointes.

Notez que la note maximale par défaut pour un nouveau devoir est de 100. Nous vous suggérons en définissant maxPoints sur une valeur autre que 100 afin de vérifier que les notes sont correctement définies. Pour l'exemple, définissez maxPoints sur 50:

Python

Ajoutez le champ maxPoints lorsque vous créez l'objet attachment. avant d'envoyer une requête CREATE au Point de terminaison courses.courseWork.addOnAttachments. Vous le trouverez dans le 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 allez également stocker la valeur maxPoints dans votre base de données locale de rattachements ; Cela évite d'avoir à effectuer un appel d'API supplémentaire lors de la notation des devoirs. Notez toutefois qu'il est possible que modifier les paramètres de notation des devoirs indépendamment de votre module complémentaire. Envoyer une requête GET au point de terminaison courses.courseWork pour afficher valeur maxPoints au niveau de l'attribution. Lors de cette opération, transmettez itemId dans CourseWork.id.

À présent, mettez à jour votre modèle de base de données pour qu'il contienne également la valeur maxPoints du rattachement. Nous vous recommandons d'utiliser la valeur maxPoints de la réponse CREATE:

Python

Tout d'abord, ajoutez un champ max_points à la table Attachment. Vous trouverez dans le fichier webapp/models.py si vous suivez l'exemple fourni.

# 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. Magasin 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 est désormais attribuée à la pièce jointe. Vous devriez pouvoir tester ce comportement maintenant ; ajouter une pièce jointe à un nouveau devoir et observez que la carte de pièce jointe affiche le lien "Synchronisation des notes" et les "Points" du devoir des changements de valeur.

Définir la note d'un élève lors de l'envoi dans Classroom

Cette section explique comment définir le numérateur d'une note de pièce jointe. c'est-à-dire le score d'un élève pour la pièce jointe. Pour ce faire, définissez un nom d'élève la valeur pointsEarned de l'envoi.

Vous devez à présent prendre une décision importante: comment le problème de votre module complémentaire devrait-il être demander de définir pointsEarned ?

Le problème est que la configuration de pointsEarned nécessite le champ d'application OAuth teacher. Vous ne devez pas accorder le niveau d'accès teacher à des élèves. cela pourrait entraîner comportement inattendu lors des interactions des élèves avec votre module complémentaire, par exemple en chargeant le iFrame de la vue enseignant au lieu de l'iFrame de la vue élève. Vous avez donc deux options de définition de pointsEarned:

  • Avec les identifiants de l'enseignant connecté
  • Utiliser des identifiants d'enseignant stockés (hors connexion)

Les sections suivantes abordent les compromis de chaque approche en démontrant chaque implémentation. Notez que les exemples fournis illustrent les deux approches pour transmettre une note dans Classroom ; consultez les des instructions spécifiques à la langue ci-dessous pour savoir comment choisir l'approche exécutant les exemples fournis:

Python

Recherchez la déclaration SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS en haut de la page. sur webapp/attachment_routes.py. Définissez cette valeur sur True pour renvoyer les notes à l'aide des identifiants de l'enseignant connecté. Définissez cette valeur sur False. pour transmettre les notes à l'aide des identifiants enregistrés lorsque l'élève envoie son activité.

Définir les notes à l'aide des identifiants de l'enseignant connecté

Utilisez les identifiants de l'utilisateur connecté pour envoyer la requête permettant de définir pointsEarned. Cette approche doit sembler assez intuitive, car elle reflète le reste de l'implémentation. jusqu'à présent et demande peu d'effort à réaliser.

Attention : l'enseignant n'interagit qu'avec l'adresse e-mail de l'élève dans l'iFrame de révision des devoirs des élèves. Il y a des éléments importants implications:

  • Aucune note n'est attribuée dans Classroom tant que l'enseignant n'a pas reçu dans l'UI de Classroom.
  • Un enseignant peut avoir à ouvrir chaque devoir d'un élève pour remplir tous les champs les notes des élèves.
  • Un court délai s'écoule avant que Classroom ne reçoive la note. et son apparence dans l'interface utilisateur de Classroom. Le délai est de généralement de cinq à dix secondes, mais peut durer jusqu'à 30 secondes.

La combinaison de ces facteurs implique que les enseignants devront peut-être des tâches manuelles considérables et chronophages pour remplir entièrement les notes d'un cours.

Pour implémenter cette approche, ajoutez un appel d'API supplémentaire à votre classe Student l'itinéraire d'examen des tâches.

Après avoir récupéré les devoirs et les pièces jointes de l'élève, évaluez le envoyé par l'élève et stocker 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 le AddOnAttachmentStudentSubmission dans le corps de la requête. Notez que nous devez également spécifier pointsEarned dans le updateMask de 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 d'identifiants d'enseignant hors connexion

La deuxième méthode pour définir les notes nécessite l'utilisation d'identifiants stockés. pour l'enseignant qui a créé la pièce jointe. Cette implémentation nécessite vous créez vos identifiants en actualisant les données d'un enseignant précédemment autorisé des jetons d'accès, puis utilisez ces identifiants pour définir pointsEarned.

L'un des principaux avantages de cette approche est que les notes sont remplies sans qu'il soit nécessaire des actions des enseignants dans l'interface utilisateur de Classroom, en évitant les problèmes mentionnés ci-dessus. Résultat : les utilisateurs finaux perçoivent l'expérience de notation de manière fluide et efficace. De plus, cette approche vous permet de choisir moment où vous transmettez des notes, par exemple lorsque les élèves terminent le cours ou de manière asynchrone.

Pour implémenter cette approche, effectuez les tâches suivantes:

  1. Modifier les enregistrements de la base de données des utilisateurs pour stocker un jeton d'accès.
  2. Modifiez les enregistrements de la base de données des pièces jointes pour stocker un ID d'enseignant.
  3. Récupérer les identifiants de l'enseignant et (éventuellement) créer un Instance de service Classroom.
  4. Définir la note d'un devoir

Pour les besoins de cette démonstration, attribuez une note lorsque l'élève termine l'activité ; c'est-à-dire lorsqu'il envoie le formulaire dans la vue élève via un routage réseau.

Modifier les enregistrements de la base de données des utilisateurs 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 cette série de tutoriels, Le schéma de la table User devrait déjà stocker un jeton d'actualisation. Enregistrer l'actualisation est suffisant lorsque vous n'effectuez des appels d'API qu'avec l'utilisateur connecté, vous recevez un jeton d'accès dans le cadre du flux d'authentification.

Cependant, vous devez maintenant passer des appels en tant qu'utilisateur autre que l'utilisateur connecté, ce qui signifie que le flux d'authentification n'est pas disponible. Vous devez donc stocker en même temps que 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, elle se trouve dans le 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, elle se trouve dans le 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 attribuer une note à une activité, passez un appel afin de définir pointsEarned comme enseignant dans le cours. Il existe plusieurs façons d'y parvenir:

  • Stockez un mappage local 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 terminaison courses de l'API Classroom pour obtenir le ou les enseignants actuels. Ensuite, interrogez les enregistrements d’utilisateurs locaux pour localiser avec les mêmes identifiants d'enseignant.
  • Lorsque vous créez une pièce jointe de module complémentaire, enregistrez l'ID d'enseignant dans le de la base de données des rattachements. Ensuite, récupérez les identifiants de l'enseignant attachmentId transmis à l'iFrame "Vue élève".

Cet exemple illustre la dernière option puisque vous définissez des notes lorsque le qu'un élève remplit une pièce jointe d'activité.

Ajoutez un champ d'ID d'enseignant à la table Attachment de votre base de données:

Python

Dans l'exemple fourni, elle se trouve dans le 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))

Ensuite, mettez à jour tout code qui crée ou met à jour un enregistrement Attachment pour qu'il enregistrer l'ID du créateur:

Python

Dans l'exemple fourni, elle se trouve dans la méthode create_attachments du 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érer les identifiants de l'enseignant

Recherchez la route menant à l'iFrame "Vue élève". Immédiatement après le stockage la réponse de l'élève dans votre base de données locale, récupérez les données depuis votre espace de stockage local. Cela devrait être simple étant donné que au cours des deux étapes précédentes. Vous pouvez également les utiliser pour créer instance du service Classroom pour l'utilisateur enseignant:

Python

Dans l'exemple fourni, elle se trouve dans la méthode load_activity_attachment de le 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 est la même que celle qui consiste à utiliser l'ID de l'enseignant identifiants. Notez toutefois que vous devez contacter l'enseignant identifiants 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 le tutoriel précédent, créez un devoir avec un élément de type pièce jointe en tant qu'enseignant, envoyer une réponse en tant qu'élève, puis ouvrir dans l'iFrame de révision des devoirs des élèves. Vous devriez voir les notes à différents moments en fonction de votre approche de mise en œuvre:

  • Si vous avez choisi de transmettre une note à l'élève qui a terminé l'activité, vous devriez déjà voir la note temporaire dans l'interface utilisateur avant d'ouvrir le IFrame d'examen des devoirs des élèves. Il apparaît également dans la liste des élèves lorsque qui ouvre le devoir, puis dans la colonne "Note" à côté de "Devoirs des élèves" Examinez l'iFrame.
  • Si vous avez choisi de transmettre une note lorsque l'enseignant ouvre le devoir Examiner l'iFrame, la note doit apparaître dans la section "Note" peu de temps après la balise de l'iFrame. Comme indiqué ci-dessus, cette opération peut prendre jusqu'à 30 secondes. Par la suite, la note pour cet élève devrait également apparaître dans la d'autres affichages du carnet de notes Classroom.

Vérifiez que la note affichée pour l'élève est correcte.

Félicitations ! Vous pouvez passer à l'étape suivante: créer des pièces jointes. en dehors de Google Classroom.