Este es el sexto instructivo de la serie de instructivos sobre complementos de Classroom.
En este instructivo, modificarás el ejemplo del paso anterior para producir un adjunto de tipo de actividad calificado. También puedes devolver una calificación a Google Classroom de forma programática, que aparecerá en el libro de calificaciones del profesor como una calificación de borrador.
Este instructivo difiere ligeramente de otros de la serie, ya que presenta dos enfoques posibles para devolver las calificaciones a Classroom. Ambos tienen impactos distintos en las experiencias del desarrollador y del usuario, por lo que debes tenerlos en cuenta al diseñar tu complemento de Classroom. Consulta nuestra página de guía sobre la interacción con archivos adjuntos para obtener más información sobre las opciones de implementación.
Ten en cuenta que las funciones de calificación de la API son opcionales. Se pueden usar con cualquier adjunto de tipo de actividad.
Durante este recorrido, completarás las siguientes tareas:
- Modifica las solicitudes de creación de adjuntos anteriores a la API de Classroom para establecer también el denominador de la calificación del adjunto.
- Califica de forma programática el envío del estudiante y establece el numerador de la calificación del adjunto.
- Implementa dos enfoques para pasar la calificación del envío a Classroom con credenciales de profesor sin conexión o con acceso.
Una vez que finalice, las calificaciones aparecerán en el registro de calificaciones de Classroom después de que se active el comportamiento de devolución. El momento exacto en que esto sucede depende del enfoque de implementación.
Para los fines de este ejemplo, reutiliza la actividad de la guía anterior, en la que se le muestra a un estudiante una imagen de un lugar de interés famoso y se le pide que ingrese su nombre. Asigna la calificación completa al adjunto si el estudiante ingresa el nombre correcto; de lo contrario, asigna cero.
Información sobre la función de calificación de la API de complementos de Classroom
Tu complemento puede establecer tanto el numerador como el denominador de la calificación de un adjunto. Estos valores se configuran, respectivamente, con los valores pointsEarned
y maxPoints
en la API. Una tarjeta de adjunto en la IU de Classroom muestra el valor de maxPoints
cuando se configuró.
Figura 1: La IU de creación de tareas con tres tarjetas de archivos adjuntos de complementos que tienen maxPoints
establecido.
La API de complementos de Classroom te permite configurar los parámetros y establecer la puntuación obtenida para las calificaciones de adjuntos. No son los mismos que las calificaciones de las tareas. Sin embargo, la configuración de la calificación de la tarea sigue la configuración de la calificación del archivo adjunto que tiene la etiqueta Sincronización de calificaciones en su tarjeta de archivo adjunto. Cuando el archivo adjunto "Sincronización de calificaciones" establece pointsEarned
para la entrega de un estudiante, también establece la calificación preliminar del estudiante para la tarea.
Por lo general, la primera adjunta que se agregó a la tarea que establece maxPoints
recibe la etiqueta "Sincronización de calificaciones". Consulta el ejemplo de la IU de creación de tareas que se muestra en la Figura 1 para ver un ejemplo de la etiqueta "Sincronización de calificaciones". Ten en cuenta que la tarjeta "Adjunto 1" tiene la etiqueta "Sincronización de calificaciones" y que la calificación de la tarea en el cuadro rojo se actualizó a 50 puntos. Ten en cuenta también que, aunque la figura 1 muestra tres tarjetas de adjuntos, solo una tiene la etiqueta "Sincronización de calificaciones". Esta es una limitación clave de la implementación actual: solo un adjunto puede tener la etiqueta "Sincronización de calificaciones".
Si hay varios archivos adjuntos con maxPoints
configurado, quitar el archivo adjunto con la opción "Sincronización de calificaciones" no habilita la "Sincronización de calificaciones" en ninguno de los archivos adjuntos restantes. Si agregas otro adjunto que establezca maxPoints
, se habilitará la Sincronización de calificaciones en el nuevo adjunto y se ajustará la calificación máxima de la tarea para que coincida. No hay ningún mecanismo para ver de forma programática qué adjunto tiene la etiqueta "Sincronización de calificaciones" ni para ver cuántos adjuntos tiene una tarea en particular.
Cómo establecer la calificación máxima de un archivo adjunto
En esta sección, se describe cómo establecer el denominador para la calificación de un adjunto, es decir, la puntuación máxima posible que todos los estudiantes pueden obtener por sus envíos. Para ello, establece el valor maxPoints
del adjunto.
Solo se necesita una pequeña modificación en nuestra implementación existente para habilitar las funciones de calificación. Cuando crees un adjunto, agrega el valor maxPoints
en el mismo objeto AddOnAttachment
que contiene los campos studentWorkReviewUri
, teacherViewUri
y otros campos de adjuntos.
Ten en cuenta que la puntuación máxima predeterminada para una tarea nueva es 100. Te sugerimos que establezcas maxPoints
en un valor distinto de 100 para que puedas verificar que las calificaciones se establezcan correctamente. Establece maxPoints
en 50 como demostración:
Python
Agrega el campo maxPoints
cuando construyas el objeto attachment
, justo antes de emitir una solicitud CREATE
al extremo courses.courseWork.addOnAttachments
. Puedes encontrarlo en el archivo webapp/attachment_routes.py
si sigues el ejemplo que proporcionamos.
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}",
}
Para los fines de esta demostración, también almacenarás el valor de maxPoints
en tu base de datos local de Attachment. Esto te ahorrará tener que realizar una llamada a la API adicional más adelante cuando califiques los envíos de los estudiantes. Sin embargo, ten en cuenta que es posible que los profesores modifiquen la configuración de las calificaciones de las tareas independientemente de tu complemento. Envía una solicitud GET
al extremo courses.courseWork
para ver el valor de maxPoints
a nivel de la asignación. Cuando lo hagas, pasa itemId
en el campo CourseWork.id
.
Ahora actualiza tu modelo de base de datos para que también contenga el valor maxPoints
del adjunto.
Te recomendamos que uses el valor maxPoints
de la respuesta CREATE
:
Python
Primero, agrega un campo max_points
a la tabla Attachment
. Puedes encontrarlo en el archivo webapp/models.py
si sigues nuestro ejemplo.
# 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)
Regresa a la solicitud courses.courseWork.addOnAttachments
CREATE
. Almacena el valor de maxPoints
que se muestra en la respuesta.
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()
Ahora el adjunto tiene una calificación máxima. Ahora deberías poder probar este comportamiento. Agrega un archivo adjunto a una tarea nueva y observa que la tarjeta del archivo adjunto muestra la etiqueta "Sincronización de calificaciones" y que cambia el valor de "Puntos" de la tarea.
Cómo establecer una calificación para la entrega de un estudiante en Classroom
En esta sección, se describe cómo establecer el numerador para la calificación de un adjunto, es decir, la puntuación de un estudiante individual para el adjunto. Para ello, establece el valor pointsEarned
de la entrega de un archivo adjunto del estudiante.
Ahora debes tomar una decisión importante: ¿cómo debe emitir tu complemento una solicitud para establecer pointsEarned
?
El problema es que configurar pointsEarned
requiere el permiso de OAuth teacher
.
No debes otorgar el alcance teacher
a los usuarios estudiantes, ya que esto podría generar un comportamiento inesperado cuando los estudiantes interactúen con tu complemento, como cargar el iframe de la vista del profesor en lugar del iframe de la vista del estudiante. Por lo tanto, tienes dos opciones para establecer pointsEarned
:
- Usar las credenciales del profesor que accedió
- Se usan credenciales de profesor almacenadas (sin conexión).
En las siguientes secciones, se analizan las ventajas y desventajas de cada enfoque antes de demostrar cada implementación. Ten en cuenta que los ejemplos que proporcionamos demuestran ambos enfoques para pasar una calificación a Classroom. Consulta las instrucciones específicas del idioma a continuación para ver cómo seleccionar un enfoque cuando ejecutes los ejemplos proporcionados:
Python
Busca la declaración SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS
en la parte superior del archivo webapp/attachment_routes.py
. Establece este valor en True
para devolver las calificaciones con las credenciales del profesor que accedió. Establece este valor en False
para devolver las calificaciones con las credenciales almacenadas cuando el estudiante envíe la actividad.
Establece calificaciones con las credenciales del profesor que accedió
Usa las credenciales del usuario que accedió para enviar la solicitud para establecer pointsEarned
.
Esto debería parecer bastante intuitivo, ya que refleja el resto de la implementación hasta el momento y requiere poco esfuerzo para realizarse.
Sin embargo, ten en cuenta que el profesor solo interactúa con el envío del estudiante en el iframe de Revisión de trabajos de estudiantes. Esto tiene algunas implicaciones importantes:
- Las calificaciones no se completan en Classroom hasta que el profesor realiza una acción en la IU de Classroom.
- Es posible que un profesor deba abrir cada entrega de los estudiantes para completar todas las calificaciones.
- Hay un breve retraso entre el momento en que Classroom recibe la calificación y el momento en que aparece en la IU de Classroom. La demora suele ser de entre cinco y diez segundos, pero puede ser de hasta 30 segundos.
La combinación de estos factores significa que los profesores pueden tener que realizar un trabajo manual considerable y que requiere mucho tiempo para completar las calificaciones de una clase.
Para implementar este enfoque, agrega una llamada a la API adicional a tu ruta existente de revisión de trabajos de estudiantes.
Después de recuperar los registros de la entrega y los archivos adjuntos del estudiante, evalúa la entrega y almacena la calificación resultante. Establece la calificación en el campo pointsEarned
de un objeto AddOnAttachmentStudentSubmission
. Por último, envía una solicitud PATCH
al extremo courses.courseWork.addOnAttachments.studentSubmissions
con la instancia AddOnAttachmentStudentSubmission
en el cuerpo de la solicitud. Ten en cuenta que también debemos especificar pointsEarned
en el updateMask
de nuestra solicitud 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()
Cómo establecer calificaciones con credenciales de profesor sin conexión
El segundo enfoque para establecer calificaciones requiere el uso de credenciales almacenadas para el profesor que creó el adjunto. Esta implementación requiere que construyas credenciales con los tokens de acceso y actualización de un profesor autorizado previamente y, luego, uses estas credenciales para establecer pointsEarned
.
Una ventaja fundamental de este enfoque es que las calificaciones se completan sin necesidad de que el profesor realice ninguna acción en la IU de Classroom, lo que evita los problemas mencionados anteriormente. El resultado es que los usuarios finales perciben la experiencia de calificación como fluida y eficiente. Además, este enfoque te permite elegir el momento en el que devuelves las calificaciones, por ejemplo, cuando los estudiantes completan la actividad o de forma asíncrona.
Completa las siguientes tareas para implementar este enfoque:
- Modifica los registros de la base de datos de usuarios para almacenar un token de acceso.
- Modifica los registros de la base de datos de Attachment para almacenar un ID de profesor.
- Recupera las credenciales del profesor y, de manera opcional, crea una nueva instancia del servicio de Classroom.
- Establece la calificación de un envío.
Para los fines de esta demostración, establece la calificación cuando el estudiante complete la actividad, es decir, cuando envíe el formulario en la ruta de la vista del estudiante.
Modifica los registros de la base de datos de usuarios para almacenar el token de acceso
Se requieren dos tokens únicos para realizar llamadas a la API: el token de actualización y el token de acceso. Si seguiste la serie de instrucciones hasta ahora, tu esquema de la tabla User
ya debería almacenar un token de actualización. Almacenar el token de actualización es suficiente cuando solo realizas llamadas a la API con el usuario que accedió, ya que recibes un token de acceso como parte del flujo de autenticación.
Sin embargo, ahora debes realizar llamadas como alguien que no es el usuario que accedió, lo que significa que el flujo de autenticación no está disponible. Por lo tanto, debes almacenar el token de acceso junto con el token de actualización. Actualiza el esquema de la tabla User
para incluir un token de acceso:
Python
En el ejemplo que proporcionamos, se encuentra en el archivo 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())
Luego, actualiza cualquier código que cree o actualice un registro de User
para que también almacene el token de acceso:
Python
En el ejemplo que proporcionamos, se encuentra en el archivo 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()
Modifica los registros de la base de datos de Attachment para almacenar un ID de profesor
Para establecer una calificación para una actividad, llama a pointsEarned
como profesor del curso. Hay varias formas de lograrlo:
- Almacena una asignación local de las credenciales del profesor a los IDs de los cursos. Sin embargo, ten en cuenta que el mismo profesor no siempre estará asociado a un curso en particular.
- Envía solicitudes de
GET
al extremocourses
de la API de Classroom para obtener los profesores actuales. Luego, consulta los registros de usuarios locales para ubicar las credenciales de profesor que coincidan. - Cuando crees un adjunto del complemento, almacena un ID de profesor en la base de datos de adjuntos local. Luego, recupera las credenciales del profesor del
attachmentId
que se pasó al iframe de la vista del estudiante.
En este ejemplo, se muestra la última opción, ya que estableces calificaciones cuando el estudiante completa un archivo adjunto de actividad.
Agrega un campo de ID de profesor a la tabla Attachment
de tu base de datos:
Python
En el ejemplo que proporcionamos, se encuentra en el archivo 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))
Luego, actualiza cualquier código que cree o actualice un registro de Attachment
para que también almacene el ID del creador:
Python
En el ejemplo que proporcionamos, esto se encuentra en el método create_attachments
del archivo 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()
Recupera las credenciales del profesor
Busca la ruta que entrega el iframe de la vista del estudiante. Inmediatamente después de almacenar la respuesta del estudiante en tu base de datos local, recupera las credenciales del profesor de tu almacenamiento local. Esto debería ser sencillo, dada la preparación de los dos pasos anteriores. También puedes usarlos para crear una instancia nueva del servicio de Classroom para el usuario profesor:
Python
En el ejemplo que proporcionamos, esto se encuentra en el método load_activity_attachment
del archivo 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)
Cómo establecer la calificación de un envío
El procedimiento a partir de aquí es idéntico al de usar las credenciales del profesor que accedió. Sin embargo, ten en cuenta que debes realizar la llamada con las credenciales del profesor que recuperaste en el paso anterior:
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()
Prueba el complemento
Al igual que en la guía anterior, crea una tarea con un archivo adjunto de tipo actividad como profesor, envía una respuesta como estudiante y, luego, abre el envío en el iframe de Revisión del trabajo de los estudiantes. Deberías poder ver la calificación en diferentes momentos según tu enfoque de implementación:
- Si elegiste devolver una calificación cuando el estudiante completó la actividad, ya deberías ver su borrador de calificación en la IU antes de abrir el iframe de Revisión del trabajo del estudiante. También puedes verlo en la lista de estudiantes cuando abres la tarea y en el cuadro "Calificación" junto al iframe de revisión del trabajo del estudiante.
- Si elegiste devolver una calificación cuando el profesor abre el iframe de Revisión del trabajo del estudiante, la calificación debería aparecer en el cuadro "Calificación" poco después de que se cargue el iframe. Como se mencionó anteriormente, este proceso puede tardar hasta 30 segundos. A partir de entonces, la calificación del estudiante específico también debería aparecer en las otras vistas del libro de calificaciones de Classroom.
Confirma que aparezca la puntuación correcta para el estudiante.
¡Felicitaciones! Ya puedes continuar con el siguiente paso: crear archivos adjuntos fuera de Google Classroom.