Anexos de tipo de conteúdo

Este é o quarto tutorial da série de complementos do Google Sala de Aula.

Neste tutorial, você interage com a API Google Classroom para criar anexos. Você fornece rotas para que os usuários visualizem o conteúdo do anexo. As visualizações diferem dependendo do papel do usuário na classe. Neste tutorial, abordamos os anexos de tipo de conteúdo, que não exigem o envio de um estudante.

Neste tutorial, você vai concluir as seguintes etapas:

  • Recupere e use os seguintes parâmetros de consulta de complementos:
    • addOnToken: um token de autorização transmitido para a visualização de descoberta de anexos.
    • itemId: um identificador exclusivo para o CourseWork, o CourseWorkMaterial ou o comunicado que recebe o anexo de complementos.
    • itemType: "courseWork", "courseWorkMaterials" ou "aviso".
    • courseId: identificador exclusivo do curso do Google Sala de Aula em que a atividade está sendo criada.
    • attachmentId: um identificador exclusivo atribuído pelo Google Sala de Aula a um anexo de complemento após a criação.
  • Implementar armazenamento permanente para anexos de tipo de conteúdo.
  • Forneça rotas para criar anexos e exibir os iframes de visualização do professor e dos estudantes.
  • Emita as seguintes solicitações para a API de complementos do Google Sala de Aula:
    • Crie um novo anexo.
    • Confira o contexto do complemento, que identifica se o usuário conectado é um estudante ou um professor.

Quando terminar, você poderá criar anexos de tipo de conteúdo nas atividades pela interface do Google Sala de Aula quando tiver feito login como professor. Os professores e alunos da turma também podem ver o conteúdo.

Ativar a API Classroom

Faça chamadas para a API Classroom começando por esta etapa. A API precisa estar ativada para seu projeto do Google Cloud antes de fazer chamadas a ela. Navegue até a entrada da biblioteca da API Google Classroom e escolha Ativar.

Processar os parâmetros de consulta da visualização de descoberta de anexos

Conforme discutido anteriormente, o Google Sala de Aula transmite parâmetros de consulta ao carregar a visualização de descoberta de anexos no iframe:

  • courseId: o ID do curso atual do Google Sala de Aula.
  • itemId: um identificador exclusivo para o CourseWork, o CourseWorkMaterial ou o comunicado que recebe o anexo de complementos.
  • itemType: "courseWork", "courseWorkMaterials" ou "aviso".
  • addOnToken: um token usado para autorizar determinadas ações de complementos do Google Sala de Aula.
  • login_hint: o ID do Google do usuário atual.
  • hd: o domínio de host do usuário atual, como example.com.

Este tutorial aborda courseId, itemId, itemType e addOnToken. Retenha e transmita esses dados ao emitir chamadas para a API Classroom.

Como na etapa anterior, armazene os valores do parâmetro de consulta transmitidos na nossa sessão. É importante fazer isso quando a visualização de descoberta de anexos é aberta pela primeira vez, já que essa é a única oportunidade para o Google Sala de Aula transmitir esses parâmetros de consulta.

Python

Navegue até o arquivo do servidor Flask que fornece rotas para a visualização de descoberta de anexos (attachment-discovery-routes.py se você estiver seguindo nosso exemplo fornecido). Na parte de cima da rota de destino do complemento (/classroom-addon no exemplo fornecido), recupere e armazene os parâmetros de consulta courseId, itemId, itemType e 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")

Grave esses valores na sessão somente se eles estiverem presentes. Eles não serão transmitidos novamente se o usuário retornar à visualização de descoberta de anexos posteriormente sem fechar o iframe.

Adicionar armazenamento permanente para anexos de tipo de conteúdo

Você precisa de um registro local de todos os anexos criados. Isso permite que você pesquise o conteúdo selecionado pelo professor usando os identificadores fornecidos pelo Google Sala de Aula.

Configure um esquema de banco de dados para uma Attachment. Nosso exemplo apresenta anexos que mostram uma imagem e uma legenda. Um Attachment contém os seguintes atributos:

  • attachment_id: identificador exclusivo de um anexo. Atribuído pelo Sala de Aula e retornado na resposta ao criar um anexo.
  • image_filename: o nome de arquivo local da imagem a ser exibida.
  • image_caption: a legenda que será exibida com a imagem.

Python

Estenda a implementação do SQLite e flask_sqlalchemy das etapas anteriores.

Navegue até o arquivo em que você definiu a tabela "Usuário" (models.py se estiver seguindo o exemplo fornecido). Adicione o seguinte na parte de baixo do arquivo, abaixo da classe 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))

Importe a nova classe de anexos para o arquivo do servidor com o anexo processando as rotas.

Configurar novos trajetos

Comece esta etapa de instruções configurando algumas páginas novas no aplicativo. Eles permitem que o usuário crie e acesse conteúdo com nosso complemento.

Adicionar rotas de criação de anexos

Você precisa de páginas para que o professor selecione o conteúdo e envie solicitações de criação de anexos. Implemente a rota /attachment-options para mostrar opções de conteúdo para o professor selecionar. Você também precisa de modelos para as páginas de confirmação de seleção e criação de conteúdo. Os exemplos fornecidos contêm modelos para isso e também podem exibir as solicitações e respostas da API Classroom.

Como alternativa, você pode modificar a página de destino da visualização de descoberta de anexos atual para exibir as opções de conteúdo em vez de criar a nova página /attachment-options. Recomendamos criar uma nova página para os fins deste exercício para preservar o comportamento do SSO implementado na segunda etapa do tutorial, como a revogação das permissões do app. Eles serão úteis ao criar e testar seu complemento.

Um professor pode selecionar entre um pequeno conjunto de imagens legendadas no nosso exemplo fornecido. Fornecemos quatro imagens de pontos de referência famosos, cujas legendas são derivadas dos nomes de arquivo.

Python

Em nosso exemplo fornecido, isso está no arquivo 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,
    )

A página "Criar anexos" será criada com esta aparência:

Exemplo de visualização de seleção de conteúdo em Python

A professora pode selecionar várias imagens. Crie um anexo para cada imagem que o professor selecionou no método create_attachments.

Emitir solicitações de criação de anexos

Agora que você sabe quais conteúdos o professor quer anexar, envie solicitações à API Classroom para criar anexos na nossa atividade. Armazene os detalhes do anexo no banco de dados depois de receber uma resposta da API Classroom.

Para começar, acesse uma instância do serviço do Google Sala de Aula:

Python

Em nosso exemplo fornecido, isso está no arquivo 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)

Emita uma solicitação CREATE para o endpoint courses.courseWork.addOnAttachments. Para cada imagem selecionada pelo professor, primeiro construa um objeto AddOnAttachment:

Python

No exemplo fornecido, isso é uma continuação do 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}",
    }

Pelo menos os campos teacherViewUri, studentViewUri e title precisam ser fornecidos para cada anexo. teacherViewUri e studentViewUri representam os URLs que são carregados quando o anexo é aberto pelo respectivo tipo de usuário.

Envie o objeto AddOnAttachment no corpo de uma solicitação para o endpoint addOnAttachments apropriado. Forneça os identificadores courseId, itemId, itemType e addOnToken com cada solicitação.

Python

No exemplo fornecido, isso é uma continuação do 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()

Crie uma entrada para esse anexo no banco de dados local para carregar mais tarde o conteúdo correto. O Google Sala de Aula retorna um valor id exclusivo na resposta à solicitação de criação. Use-o como a chave primária do banco de dados. Observe também que o Google Sala de Aula transmite o parâmetro de consulta attachmentId ao abrir as visualizações de professor e estudante:

Python

No exemplo fornecido, isso é uma continuação do 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()

Considere direcionar o usuário para uma página de confirmação neste momento, reconhecendo que os anexos foram criados com sucesso.

Permitir anexos do seu complemento

Agora é um bom momento para adicionar os endereços apropriados ao campo "Prefixos do URI de anexo permitido" na página "Configuração do app do SDK do GWM". Seu complemento só pode criar anexos usando um dos prefixos de URI listados nesta página. Essa é uma medida de segurança para ajudar a reduzir a possibilidade de ataques "man-in-the-middle".

A abordagem mais simples é fornecer seu domínio de nível superior neste campo, por exemplo, https://example.com. https://localhost:<your port number>/ funcionaria se você estivesse usando sua máquina local como o servidor da Web.

Adicionar rotas para as visualizações de professor e estudante

Um complemento do Google Sala de Aula pode ser carregado por quatro iframes. Até agora, você só criou rotas que veiculam o iframe da visualização de descoberta de anexos. Em seguida, adicione rotas para exibir também os iframes de visualização de professor e estudante.

O iframe Teacher View é necessário para mostrar uma prévia da experiência do estudante, mas pode incluir mais informações ou recursos de edição.

A Visualização do estudante é a página que é apresentada a cada estudante quando ele abre um anexo de complemento.

Para este exercício, crie uma única rota /load-content-attachment que atenda à visualização de professor e estudante. Use os métodos da API Classroom para determinar se o usuário é um professor ou estudante quando a página for carregada.

Python

Em nosso exemplo fornecido, isso está no arquivo 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")

Tenha em mente que você também deve autenticar o usuário neste momento. Também é preciso processar os parâmetros de consulta login_hint e hd aqui e encaminhar o usuário para seu fluxo de autorização, se necessário. Consulte os detalhes das orientações de login discutidos nas instruções anteriores para mais informações sobre esse fluxo.

Em seguida, envie uma solicitação para o endpoint getAddOnContext que corresponde ao tipo de item.

Python

No exemplo fornecido, isso é uma continuação do 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()

Esse método retorna informações sobre o papel do usuário atual na classe. alterar a visualização apresentada ao usuário dependendo da função desempenhada pelo usuário. Exatamente um dos campos studentContext ou teacherContext é preenchido no objeto de resposta. Examine-os para determinar como abordar o usuário.

Em qualquer caso, use o valor do parâmetro de consulta attachmentId para saber qual anexo recuperar do nosso banco de dados. Esse parâmetro de consulta é fornecido ao abrir os URIs de visualização de professor ou estudante.

Python

No exemplo fornecido, isso é uma continuação do 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)

Testar o complemento

Siga estas etapas para testar a criação de anexos:

  • Faça login no [Google Sala de Aula] como um dos seus usuários de teste de Professor.
  • Acesse a guia Atividades e crie uma nova Atividade.
  • Clique no botão Complementos abaixo da área de texto e selecione o complemento. O iframe é aberto, e o complemento carrega o URI de configuração de anexos especificado na página Configuração do app do SDK do GWM.
  • Escolha um conteúdo para anexar à atividade.
  • Feche o iframe após a conclusão do fluxo de criação do anexo.

Um card de anexo será exibido na interface de criação de atividades no Google Google Sala de Aula. Clique no card para abrir o iframe da visualização do professor e confirme se o anexo correto aparece. Clique no botão Atribuir.

Siga estas etapas para testar a experiência do estudante:

  • Em seguida, faça login no Google Sala de Aula como usuário de teste estudante na mesma turma que o usuário de teste professor.
  • Encontre a atividade de teste na guia "Atividades".
  • Expanda a atividade e clique no card de anexo para abrir o iframe da visualização dos alunos.

Confirme se o anexo correto aparece para o estudante.

Parabéns! Você está pronto para prosseguir para a próxima etapa: criar anexos de tipo de atividade.