Archivos adjuntos de tipo de contenido

Esta es la cuarta explicación de la serie sobre complementos de Classroom.

En esta explicación, interactuarás con la API de Google Classroom para crear archivos adjuntos. Proporciona rutas para que los usuarios vean el contenido del archivo adjunto. Las vistas difieren según la función del usuario en la clase. En esta explicación, se abordan los archivos adjuntos por tipo de contenido, que no requieren la entrega de un estudiante.

En esta explicación, completarás lo siguiente:

  • Recupera y usa los siguientes parámetros de consulta de complementos:
    • addOnToken: Es un token de autorización que se pasa a la vista de descubrimiento de archivos adjuntos.
    • itemId: Un identificador único para el objeto CourseWork, CourseWorkMaterial o el anuncio que recibe el archivo adjunto del complemento.
    • itemType: "courseWork", "courseWorkMaterials" o "anuncio".
    • courseId: Un identificador único para el curso de Google Classroom en el que se crea la tarea.
    • attachmentId: Es un identificador único que Google Classroom asigna a un archivo adjunto del complemento después de su creación.
  • Implementa el almacenamiento persistente para los archivos adjuntos de tipo de contenido.
  • Proporciona rutas para crear archivos adjuntos y entregar los iframes de la vista de profesor y la vista de alumno.
  • Envía las siguientes solicitudes a la API de complementos de Google Classroom:
    • Crea un nuevo adjunto.
    • Obtener el contexto del complemento, que identifica si el usuario que accedió es un estudiante o un profesor

Cuando termines, puedes crear archivos adjuntos de tipo de contenido en las tareas mediante la IU de Google Classroom cuando accedas como profesor. Los profesores y los alumnos de la clase también pueden ver el contenido.

Habilita la API de Classroom

Realiza llamadas a la API de Classroom a partir de este paso. La API debe estar habilitada para tu proyecto de Google Cloud antes de que puedas hacer llamadas a ella. Navega a la entrada de la biblioteca de la API de Google Classroom y selecciona Habilitar.

Controla los parámetros de consulta de la vista de descubrimiento de archivos adjuntos

Como analizamos anteriormente, Google Classroom pasa parámetros de consulta cuando carga la vista de descubrimiento de archivos adjuntos en el iframe:

  • courseId: El ID del curso actual de Classroom
  • itemId: Un identificador único para el objeto CourseWork, CourseWorkMaterial o el anuncio que recibe el archivo adjunto del complemento.
  • itemType: Puede ser "courseWork", "courseWorkMaterials" o "anuncio".
  • addOnToken: Es un token que se usa para autorizar determinadas acciones del complemento de Classroom.
  • login_hint: Es el ID de Google del usuario actual.
  • hd: Es el dominio de host para el usuario actual, como example.com.

En esta explicación, se abordan courseId, itemId, itemType y addOnToken. Retén y pasa estos datos cuando emitas llamadas a la API de Classroom.

Al igual que en el paso de explicación anterior, almacena los valores de los parámetros de consulta que se pasaron en nuestra sesión. Es importante que lo hagamos cuando la vista de descubrimiento de archivos adjuntos se abre por primera vez, ya que esta es la única oportunidad para que Classroom pase estos parámetros de consulta.

Python

Navega al archivo del servidor Flask, que proporciona rutas para la vista de descubrimiento de archivos adjuntos (attachment-discovery-routes.py si sigues nuestro ejemplo proporcionado). En la parte superior de la ruta de destino del complemento (/classroom-addon en el ejemplo proporcionado), recupera y almacena los parámetros de consulta courseId, itemId, itemType y addOnToken.

# Retrieve the itemId, courseId, and addOnToken query parameters.
if flask.request.args.get("itemId"):
    flask.session["itemId"] = flask.request.args.get("itemId")
if flask.request.args.get("itemType"):
    flask.session["itemType"] = flask.request.args.get("itemType")
if flask.request.args.get("courseId"):
    flask.session["courseId"] = flask.request.args.get("courseId")
if flask.request.args.get("addOnToken"):
    flask.session["addOnToken"] = flask.request.args.get("addOnToken")

Escribe estos valores en la sesión solo si están presentes; no se vuelven a pasar si el usuario vuelve a la vista de descubrimiento de archivos adjuntos más adelante sin cerrar el iframe.

Agrega almacenamiento persistente para archivos adjuntos de tipo de contenido

Necesitas un registro local de los adjuntos creados. Esto te permite buscar el contenido que seleccionó el profesor con identificadores que proporcionó Classroom.

Configura un esquema de base de datos para un Attachment. En nuestro ejemplo, se incluyen archivos adjuntos que muestran una imagen y una leyenda. Attachment contiene los siguientes atributos:

  • attachment_id: Es un identificador único para un archivo adjunto. Lo asigna Classroom y se muestra en la respuesta cuando se crea un archivo adjunto.
  • image_filename: Es el nombre de archivo local de la imagen que se mostrará.
  • image_caption: Es la leyenda que se mostrará con la imagen.

Python

Extiende la implementación de SQLite y flask_sqlalchemy de los pasos anteriores.

Navega hasta el archivo en el que definiste la tabla de usuarios (models.py si sigues nuestro ejemplo proporcionado). Agrega lo siguiente en la parte inferior del archivo, debajo de la clase User.

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))

Importa la nueva clase de adjunto al archivo de servidor con tus rutas de control de archivos adjuntos.

Cómo configurar rutas nuevas

Para comenzar esta explicación, configura algunas páginas nuevas en nuestra aplicación. Estas permiten a los usuarios crear y ver contenido a través de nuestro complemento.

Agregar rutas de creación de archivos adjuntos

Necesitas páginas para que el profesor seleccione el contenido y emita solicitudes de creación de archivos adjuntos. Implementa la ruta /attachment-options para mostrar opciones de contenido que el profesor debe seleccionar. También necesitas plantillas para las páginas de selección de contenido y confirmación de creación. Los ejemplos que proporcionamos contienen plantillas para estas funciones y también pueden mostrar las solicitudes y respuestas de la API de Classroom.

Ten en cuenta que, como alternativa, puedes modificar tu página de destino existente de la vista de descubrimiento de archivos adjuntos para mostrar las opciones de contenido en lugar de crear la página nueva de /attachment-options. Te recomendamos que crees una página nueva para este ejercicio, de modo que conserves el comportamiento de SSO implementado en el segundo paso de la explicación, como la revocación de los permisos de la app. Estos deberían ser útiles a medida que compilas y pruebas tu complemento.

En el ejemplo que proporcionamos, un profesor puede seleccionar imágenes subtituladas de un pequeño conjunto. Hemos proporcionado cuatro imágenes de puntos de referencia famosos cuyas leyendas derivan de los nombres de archivo.

Python

En nuestro ejemplo proporcionado, esto se encuentra en el archivo webapp/attachment_routes.py.

@app.route("/attachment-options", methods=["GET", "POST"])
def attachment_options():
    """
    Render the attachment options page from the "attachment-options.html"
    template.

    This page displays a grid of images that the user can select using
    checkboxes.
    """

    # A list of the filenames in the static/images directory.
    image_filenames = os.listdir(os.path.join(app.static_folder, "images"))

    # The image_list_form_builder method creates a form that displays a grid
    # of images, checkboxes, and captions with a Submit button. All images
    # passed in image_filenames will be shown, and the captions will be the
    # title-cased filenames.

    # The form must be built dynamically due to limitations in WTForms. The
    # image_list_form_builder method therefore also returns a list of
    # attribute names in the form, which will be used by the HTML template
    # to properly render the form.
    form, var_names = image_list_form_builder(image_filenames)

    # If the form was submitted, validate the input and create the attachments.
    if form.validate_on_submit():

        # Build a dictionary that maps image filenames to captions.
        # There will be one dictionary entry per selected item in the form.
        filename_caption_pairs = construct_filename_caption_dictionary_list(
            form)

        # Check that the user selected at least one image, then proceed to
        # make requests to the Classroom API.
        if len(filename_caption_pairs) > 0:
            return create_attachments(filename_caption_pairs)
        else:
            return flask.render_template(
                "create-attachment.html",
                message="You didn't select any images.",
                form=form,
                var_names=var_names)

    return flask.render_template(
        "attachment-options.html",
        message=("You've reached the attachment options page. "
                "Select one or more images and click 'Create Attachment'."),
        form=form,
        var_names=var_names,
    )

Aparecerá una página "Crear archivos adjuntos" similar a la siguiente:

Vista de selección de contenido de ejemplo de Python

El profesor puede seleccionar varias imágenes. Crea un archivo adjunto para cada imagen que el profesor seleccionó en el método create_attachments.

Emitir solicitudes de creación de adjuntos

Ahora que sabes qué contenido que el profesor quiere adjuntar, emite solicitudes a la API de Classroom para crear archivos adjuntos en nuestra tarea. Almacena los detalles del archivo adjunto en tu base de datos después de recibir una respuesta de la API de Classroom.

Para comenzar, obtén una instancia del servicio Classroom:

Python

En nuestro ejemplo proporcionado, esto se encuentra en el archivo webapp/attachment_routes.py.

def create_attachments(filename_caption_pairs):
    """
    Create attachments and show an acknowledgement page.

    Args:
        filename_caption_pairs: A dictionary that maps image filenames to
            captions.
    """
    # Get the Google Classroom service.
    # We need to request the Classroom API from a specific URL while add-ons
    # are in Early Access.

    # A Google API Key can be created in your Google Cloud project's Credentials
    # settings: https://console.cloud.google.com/apis/credentials.
    # Click "Create Credentials" at top and choose "API key", then provide
    # the key in the discoveryServiceUrl below.
    classroom_service = googleapiclient.discovery.build(
        serviceName="classroom",
        version="v1",
        discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
        credentials=credentials)

Emite una solicitud CREATE al extremo courses.courseWork.addOnAttachments. Para cada imagen que seleccionó el profesor, primero construye un objeto AddOnAttachment:

Python

En nuestro ejemplo proporcionado, es una continuación del método create_attachments.

# Create a new attachment for each image that was selected.
attachment_count = 0
for key, value in filename_caption_pairs.items():
    attachment_count += 1

    # Create a dictionary with values for the AddOnAttachment object fields.
    attachment = {
        # Specifies the route for a teacher user.
        "teacherViewUri": {
            "uri":
                flask.url_for(
                    "load_content_attachment", _scheme='https', _external=True),
        },
        # Specifies the route for a student user.
        "studentViewUri": {
            "uri":
                flask.url_for(
                    "load_content_attachment", _scheme='https', _external=True)
        },
        # The title of the attachment.
        "title": f"Attachment {attachment_count}",
    }

Se deben proporcionar al menos los campos teacherViewUri, studentViewUri y title para cada adjunto. teacherViewUri y studentViewUri representan las URLs que se cargan cuando el tipo de usuario respectivo abre el archivo adjunto.

Envía el objeto AddOnAttachment en el cuerpo de una solicitud al extremo addOnAttachments correspondiente. Proporciona los identificadores courseId, itemId, itemType y addOnToken con cada solicitud.

Python

En nuestro ejemplo proporcionado, es una continuación del método create_attachments.

# Use the itemType to determine which stream item type the teacher created
match flask.session["itemType"]:
    case "announcements":
        parent = classroom_service.courses().announcements()
    case "courseWorkMaterials":
        parent = classroom_service.courses().courseWorkMaterials()
    case _:
        parent = classroom_service.courses().courseWork()

# Issue a request to create the attachment.
resp = parent.addOnAttachments().create(
    courseId=flask.session["courseId"],
    itemId=flask.session["itemId"],
    addOnToken=flask.session["addOnToken"],
    body=attachment).execute()

Crea una entrada para este adjunto en tu base de datos local a fin de que luego puedas cargar el contenido correcto. Classroom muestra un valor id único en la respuesta a la solicitud de creación, así que úsalo como clave primaria en nuestra base de datos. Además, ten en cuenta que Classroom pasa el parámetro de búsqueda attachmentId cuando se abren las vistas de profesores y alumnos:

Python

En nuestro ejemplo proporcionado, es una continuación del método create_attachments.

# Store the value 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)
db.session.add(new_attachment)
db.session.commit()

Considera dirigir al usuario a una página de confirmación en este punto y confirmar que creó los adjuntos de forma correcta.

Permitir los archivos adjuntos de tu complemento

Este es un buen momento para agregar las direcciones adecuadas al campo Prefijos de URI de archivo adjunto permitidos en la página Configuración de la app del SDK de GWM. Tu complemento solo puede crear archivos adjuntos a partir de uno de los prefijos de URI que se enumeran en esta página. Esta es una medida de seguridad para reducir la posibilidad de ataques de intermediarios.

El enfoque más simple es proporcionar tu dominio de nivel superior en este campo, por ejemplo, https://example.com. https://localhost:<your port number>/ funcionaría si usas tu máquina local como servidor web.

Cómo agregar rutas para las vistas de profesores y alumnos

Hay cuatro iframes en los que se puede cargar un complemento de Google Classroom. Hasta ahora, solo creaste rutas que entregan el iframe de vista de descubrimiento de archivos adjuntos. A continuación, agrega rutas para publicar también los iframes de la vista del profesor y del alumno.

Se requiere el iframe de Vista de profesores para mostrar una vista previa de la experiencia del estudiante, pero, de manera opcional, puede incluir información adicional o funciones de edición.

Vista de alumno es la página que se le presenta a cada estudiante cuando abre un archivo adjunto del complemento.

A los efectos de este ejercicio, crea una sola ruta /load-content-attachment que funcione tanto en la vista de profesor como en la del alumno. Usa los métodos de la API de Classroom para determinar si el usuario es profesor o alumno cuando se carga la página.

Python

En nuestro ejemplo proporcionado, esto se encuentra en el archivo webapp/attachment_routes.py.

@app.route("/load-content-attachment")
def load_content_attachment():
    """
    Load the attachment for the user's role."""

    # Since this is a landing page for the Teacher and Student View iframes, we
    # need to preserve the incoming query parameters.
    if flask.request.args.get("itemId"):
        flask.session["itemId"] = flask.request.args.get("itemId")
    if flask.request.args.get("itemType"):
        flask.session["itemType"] = flask.request.args.get("itemType")
    if flask.request.args.get("courseId"):
        flask.session["courseId"] = flask.request.args.get("courseId")
    if flask.request.args.get("attachmentId"):
        flask.session["attachmentId"] = flask.request.args.get("attachmentId")

Ten en cuenta que también debes autenticar al usuario en este punto. También debes controlar los parámetros de consulta login_hint y hd aquí, y dirigir al usuario a tu flujo de autorización si es necesario. Consulta los detalles de orientación para el acceso que se analizaron en las explicaciones anteriores a fin de obtener más información sobre este flujo.

Luego, envía una solicitud al extremo getAddOnContext que coincida con el tipo de elemento.

Python

En nuestro ejemplo proporcionado, es una continuación del método load_content_attachment.

# Create an instance of the Classroom service.
classroom_service = googleapiclient.discovery.build(
    serviceName="classroom"
    version="v1",
    discoveryServiceUrl=f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}",
    credentials=credentials)

# Use the itemType to determine which stream item type the teacher created
match flask.session["itemType"]:
    case "announcements":
        parent = classroom_service.courses().announcements()
    case "courseWorkMaterials":
        parent = classroom_service.courses().courseWorkMaterials()
    case _:
        parent = classroom_service.courses().courseWork()

addon_context_response = parent.getAddOnContext(
    courseId=flask.session["courseId"],
    itemId=flask.session["itemId"]).execute()

Este método muestra información sobre el rol del usuario actual en la clase. Alterar la vista que se le presenta al usuario según su rol Exactamente uno de los campos studentContext o teacherContext se propaga en el objeto de respuesta. Examínalos para determinar cómo dirigirte al usuario.

En cualquier caso, usa el valor del parámetro de consulta attachmentId para saber qué adjunto recuperar de nuestra base de datos. Este parámetro de búsqueda se proporciona cuando se abren los URI de vista del profesor o del alumno.

Python

En nuestro ejemplo proporcionado, es una continuación del método load_content_attachment.

# Determine which view we are in by testing the returned context type.
user_context = "student" if addon_context_response.get(
    "studentContext") else "teacher"

# Look up the attachment in the database.
attachment = Attachment.query.get(flask.session["attachmentId"])

# Set the text for the next page depending on the user's role.
message_str = f"I see that you're a {user_context}! "
message_str += (
    f"I've loaded the attachment with ID {attachment.attachment_id}. "
    if user_context == "teacher" else
    "Please enjoy this image of a famous landmark!")

# Show the content with the customized message text.
return flask.render_template(
    "show-content-attachment.html",
    message=message_str,
    image_filename=attachment.image_filename,
    image_caption=attachment.image_caption,
    responses=response_strings)

Prueba el complemento

Completa los siguientes pasos para probar la creación de archivos adjuntos:

  • Accede a [Google Classroom] como uno de los usuarios de la prueba Teacher.
  • Navega a la pestaña Trabajo en clase y crea una Tarea nueva.
  • Haz clic en el botón Complementos debajo del área de texto y, luego, selecciona tu complemento. Se abrirá el iframe y el complemento cargará el URI de configuración de archivos adjuntos que especificaste en la página Configuración de la app del SDK de GWM.
  • Elige un contenido para adjuntar a la tarea.
  • Cierra el iframe una vez que se complete el flujo de creación de archivos adjuntos.

Deberías ver una tarjeta adjunta en la IU de creación de tareas de Google Classroom. Haz clic en la tarjeta para abrir el iframe de la Vista de profesores y confirmar que aparezca el archivo adjunto correcto. Haz clic en el botón Asignar.

Para evaluar la experiencia del alumno, sigue estos pasos:

  • Luego, accede a Classroom como un usuario de prueba de estudiante en la misma clase que el usuario de prueba de profesor.
  • Busca la tarea de prueba en la pestaña Trabajo en clase.
  • Expande la tarea y haz clic en la tarjeta adjunta para abrir el iframe de Vista de alumno.

Confirma que aparezca el archivo adjunto correcto para el estudiante.

¡Felicitaciones! Ya puedes continuar con el siguiente paso: crear archivos adjuntos de tipo de actividad.