Notas de anexo e passback da nota

Este é o sexto tutorial sobre os complementos do Google Sala de Aula série de tutoriais.

Neste tutorial, você vai modificar o exemplo da etapa anterior. para produzir um anexo do tipo atividade com nota. Você também passa uma nota ao Google Sala de Aula de forma programática, que aparece na nota do professor como uma nota temporária.

Este tutorial é um pouco diferente de outros da série, porque há apresentar duas abordagens possíveis para passar notas de volta para Google Sala de Aula. Ambos têm impactos distintos sobre o desenvolvedor e o usuário experiências considere ambos ao criar seu complemento do Google Sala de Aula. Leia nossa página do guia de interação com anexos para uma discussão adicional sobre as opções de implementação.

Os recursos de avaliação na API são opcionais. Eles podem ser usados com qualquer anexo do tipo de atividade.

Neste tutorial, você vai fazer o seguinte:

  • Modifique as solicitações anteriores de criação de anexos para API Classroom para também definir o denominador de nota do anexo.
  • Dê uma pontuação programática ao envio do estudante e defina a configuração numerador de notas.
  • Implementar duas abordagens para passar a nota do trabalho para Google Sala de Aula usando credenciais de professor que fez login ou off-line.

Após a conclusão, as notas aparecem no boletim de notas do Google Sala de Aula após o comportamento de passback for acionado. O momento exato em que isso acontece depende a abordagem de implementação.

Para este exemplo, reutilize a atividade do exemplo anterior em que um estudante vê a imagem de um ponto turístico famoso e o nome dele será solicitado. Atribua notas completas para o anexo se o aluno insere o nome correto, caso contrário zero.

Entender o recurso de avaliação da API de complementos do Google Sala de Aula

Seu complemento pode definir o numerador e o denominador de nota de uma anexo. Elas são definidas usando pointsEarned e maxPoints, respectivamente. valores na API. Um card de anexo na interface do Google Sala de Aula mostra o valor maxPoints quando tiver sido definido.

Exemplo de vários anexos com maxPoints em um
atribuição

Figura 1. A interface de criação de atividades com três cartões de anexo de complementos que têm maxPoints definido.

Com a API Classroom Add-ons, é possível definir as configurações a pontuação recebida para as notas Anexo. Eles não são iguais aos atividades. No entanto, as configurações de nota das tarefas seguem as configurações de nota do anexo com o marcador Sincronização de notas ativado. o cartão de anexo. Quando a opção "Sincronizar notas" o anexo define pointsEarned para um envio de um aluno, ele também define a nota temporária dele para a tarefa.

Geralmente, o primeiro anexo adicionado à tarefa que define maxPoints recebe a mensagem "Sincronização de notas". rótulo. Conferir a interface de criação de atividades mostrado na Figura 1 para um exemplo da sincronização de notas rótulo. Observe que o "Anexo 1" o card tem a opção "Sincronizar notas". e que a nota da tarefa na caixa vermelha foi atualizada para 50 pontos. Observe também que, embora a Figura 1 mostra três cards de anexo, apenas um card tem a "Sincronização de notas" rótulo. Isso é uma limitação importante da implementação atual: somente um anexo pode ter a opção "Sincronizar notas" rótulo.

Se houver vários anexos com maxPoints definido, remova o anexo com a opção "Sincronizar notas" não ativa a opção "Sincronizar notas" em qualquer um dos anexos restantes. Adicionar outro anexo que define maxPoints ativa As notas são sincronizadas no novo anexo, e a nota máxima da tarefa é ajustada para são correspondentes. Não existe um mecanismo para ver programaticamente qual anexo tem a "Sincronização de notas" marcador nem para ver quantos anexos uma atividade específica tem.

Definir a nota máxima de um anexo

Esta seção descreve a configuração do denominador de uma nota de anexo. que é a pontuação máxima que todos os estudantes podem atingir no envios. Para fazer isso, defina o valor maxPoints do anexo.

É necessária apenas uma pequena modificação na implementação atual para ativar os recursos de avaliação. Ao criar um anexo, adicione o valor maxPoints em O mesmo objeto AddOnAttachment que contém a studentWorkReviewUri, teacherViewUri e outros campos de anexo.

A pontuação máxima padrão de uma nova atividade é 100. Sugerimos definindo maxPoints como um valor diferente de 100, para que você possa verificar se o as notas estão sendo definidas corretamente. Defina maxPoints como 50 como demonstração:

Python

Adicione o campo maxPoints ao criar o objeto attachment, apenas antes de emitir uma solicitação CREATE para o endpoint courses.courseWork.addOnAttachments. Você pode encontrar isso na webapp/attachment_routes.py, se estiver seguindo nosso exemplo fornecido.

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 esta demonstração, você também armazena o valor maxPoints em seu banco de dados de anexos local; Isso economiza a necessidade de fazer uma chamada de API adicional mais tarde ao avaliar os envios dos alunos. Observe, no entanto, que é possível os professores alteram as configurações de notas das atividades independentemente do complemento. Enviar uma solicitação GET ao endpoint courses.courseWork para consultar valor maxPoints no nível da atribuição. Ao fazer isso, transmita o itemId na CourseWork.id.

Agora atualize seu modelo de banco de dados para também armazenar o valor maxPoints do anexo. Recomendamos usar o valor maxPoints da resposta CREATE:

Python

Primeiro, adicione um campo max_points à tabela Attachment. Você encontra este no arquivo webapp/models.py, se estiver seguindo o exemplo fornecido.

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

Retorne à solicitação CREATE courses.courseWork.addOnAttachments. Armazenamento o valor maxPoints retornado na resposta.

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

Agora o anexo tem uma nota máxima. Você poderá testar esse comportamento agora; adicionar um anexo a uma nova tarefa e observar que o cartão do anexo mostra "Sincronização de notas" e os "Pontos" da atividade as mudanças de valor.

Definir a nota de envio dos estudantes no Google Sala de Aula

Esta seção descreve a configuração do numerador de uma nota de anexo. isto é, a pontuação de um estudante específico para o anexo. Para fazer isso, defina um nome de valor pointsEarned do envio.

Agora você tem uma decisão importante a tomar: como seu complemento deve enviar para definir pointsEarned?

O problema é que a configuração de pointsEarned exige o escopo teacher do OAuth. Não conceda o escopo teacher a usuários estudantes. isso pode resultar em comportamento inesperado na interação dos estudantes com seu complemento, como carregar Iframe da visualização do professor em vez do iframe da visualização dos alunos. Portanto, você tem duas opções sobre como definir pointsEarned:

  • Usando as credenciais do professor que fez login.
  • Usar credenciais de professor armazenadas (off-line).

As seções a seguir discutem as vantagens e desvantagens de cada abordagem antes para demonstrar cada implementação. Nossos exemplos mostram as duas abordagens para passar notas no Google Sala de Aula; consulte em algumas linguagens específicas para saber como selecionar uma abordagem ao executando os exemplos fornecidos:

Python

Encontre a declaração SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS na parte de cima. de webapp/attachment_routes.py. Defina esse valor como True para fazer a transmissão com as credenciais do professor que fez login. Defina esse valor como False passar as notas usando as credenciais armazenadas quando o aluno enviar o atividades.

Definir notas usando as credenciais do professor que fez login

Use as credenciais do usuário que fez login para emitir a solicitação e definir pointsEarned. Isso deve parecer bastante intuitivo, já que reflete o restante da implementação até agora e requer pouco esforço para ser alcançado.

No entanto, considere que o professor interage apenas com o sistema no iframe "Revisão dos trabalhos dos alunos". Isso tem algumas implicações:

  • Nenhuma nota é preenchida no Google Sala de Aula até que o professor tire na interface do Google Sala de Aula.
  • Um professor pode ter que abrir todos os envios de estudantes para preencher todas as notas dos alunos.
  • Há um pequeno atraso entre o recebimento das notas pelo Google Sala de Aula e a aparência dele na interface do Google Sala de Aula. O atraso é geralmente de 5 a 10 segundos, mas pode durar até 30 segundos.

A combinação desses fatores significa que os professores talvez precisem um trabalho manual considerável e demorado para preencher totalmente as notas de uma turma.

Para implementar essa abordagem, adicione mais uma chamada de API à sua conta de Trajeto da revisão do trabalho.

Após buscar o envio do estudante e os registros de anexos, avalie a o envio do aluno e armazenar a nota resultante. Defina a nota no Campo pointsEarned de um objeto AddOnAttachmentStudentSubmission. Por fim, emitir uma solicitação PATCH para o endpoint courses.courseWork.addOnAttachments.studentSubmissions com o AddOnAttachmentStudentSubmission no corpo da solicitação. Nós também é necessário especificar pointsEarned no updateMask da solicitação 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()

Definir notas usando credenciais de professor off-line

A segunda abordagem para definir notas exige o uso de credenciais armazenadas para o professor que criou o anexo. Essa implementação requer que você cria credenciais usando atualizações de um professor autorizado tokens de acesso e use essas credenciais para definir pointsEarned.

Uma vantagem essencial dessa abordagem é que as notas são preenchidas sem ação dos professores na interface do Google Sala de Aula, evitando os problemas mencionado acima. Os usuários finais percebem a experiência de avaliação de forma simples e eficiente. Além disso, essa abordagem permite escolher momento em que você passa as notas, como quando os estudantes concluem as ou de forma assíncrona.

Conclua as tarefas a seguir para implementar essa abordagem:

  1. Modifique os registros do banco de dados do usuário para armazenar um token de acesso.
  2. Modifique os registros do banco de dados de anexos para armazenar um ID de professor.
  3. Recupere as credenciais do professor e, se quiser, crie um novo Instância de serviço do Sala de Aula.
  4. Definir a nota de uma atividade.

Para esta demonstração, defina a nota quando o aluno terminar a atividade; ou seja, quando o estudante envia o formulário na visualização do estudante trajeto.

Modifique os registros do banco de dados do usuário para armazenar o token de acesso

São necessários dois tokens exclusivos para fazer chamadas de API: o token de atualização e o token de acesso. Se você tem seguido a série de tutoriais até agora, seu O esquema de tabela User já deve armazenar um token de atualização. Como armazenar a atualização é suficiente quando você só fizer chamadas de API com o usuário conectado, conforme você recebe um token de acesso como parte do fluxo de autenticação.

No entanto, agora você precisa fazer chamadas como outra pessoa que não seja o usuário que fez login. ou seja, o fluxo de autenticação não está disponível. Portanto, você precisa armazenar com o token de atualização. Atualize o esquema da tabela User para incluem um token de acesso:

Python

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

Em seguida, atualize qualquer código que crie ou atualize um registro User para também armazenar o token de acesso:

Python

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

Modificar registros do banco de dados de anexos para armazenar um ID de professor

Para dar a nota a uma atividade, chame pointsEarned como professor do curso. Há várias maneiras de fazer isso:

  • Armazenar um mapeamento local das credenciais de professores para os IDs dos cursos. Observe, no entanto, mesmo professor nem sempre está associado a um curso específico.
  • Emitir solicitações GET para o endpoint courses da API Classroom para encontrar os professores atuais. Em seguida, consulte os registros locais dos usuários com as credenciais do professor.
  • Ao criar um anexo de complemento, armazene um ID de professor no no banco de dados de anexos. Em seguida, recupere as credenciais de professor na attachmentId transmitido para o iframe do Student View.

Este exemplo demonstra a última opção, já que você está definindo notas quando o o aluno conclui um anexo de atividade.

Adicione um campo de ID de professor à tabela Attachment do banco de dados:

Python

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

Em seguida, atualize qualquer código que crie ou atualize um registro Attachment para também armazenar o ID do criador:

Python

No exemplo fornecido, isso está no método create_attachments na arquivo 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()

Recuperar as credenciais do professor

Encontre o trajeto que veicula o iframe do Student View. Imediatamente após o armazenamento a resposta do aluno em seu banco de dados local, recupere a resposta credenciais do seu armazenamento local. Isso deve ser simples, considerando que de preparação nas duas etapas anteriores. Você também pode usá-los para criar um novo instância do serviço do Google Sala de Aula para o usuário professor:

Python

No exemplo fornecido, isso está no método load_activity_attachment da no arquivo 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)

Definir a nota de um envio

O procedimento aqui é idêntico ao de usar a conta do professor que fez login credenciais. Mas você deve conversar com o professor credenciais recuperadas na etapa 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()

Testar o complemento

Semelhante ao tutorial anterior, crie uma atividade com um tipo de atividade anexo como professor, enviar uma resposta como aluno e abrir no iframe "Revisão dos trabalhos dos alunos". Aparecerá nota aparecem em momentos diferentes, dependendo da abordagem de implementação:

  • Se você optar por transmitir uma nota quando o aluno concluir a atividade, você já deve ver a nota temporária na interface antes de abrir o iframe "Revisão dos trabalhos dos alunos". Ele também aparece na lista de alunos quando abrindo a tarefa e, na coluna "Nota", caixa ao lado de "Trabalhos dos alunos" Revisar iframe.
  • Se você escolheu transmitir uma nota quando o professor abrir os trabalhos dos alunos Revisar iframe. A nota vai aparecer em "Nota". logo após o o iframe é carregado. Conforme mencionado acima, isso pode levar até 30 segundos. Depois disso, a nota do aluno específico também deve aparecer na outras visualizações do boletim de notas do Google Sala de Aula.

Confirme se aparece a pontuação correta para o estudante.

Parabéns! Você está pronto para prosseguir para a próxima etapa: como criar anexos fora do Google Sala de Aula.