Вложения типа контента

Это четвертое прохождение в серии прохождений дополнений Класса.

В этом пошаговом руководстве вы взаимодействуете с API Google Classroom для создания вложений. Вы предоставляете пользователям маршруты для просмотра содержимого вложения. Представления различаются в зависимости от роли пользователя в классе. В этом пошаговом руководстве рассматриваются вложения типа контента , которые не требуют отправки от учащегося.

В ходе этого прохождения вы выполните следующее:

  • Получите и используйте следующие дополнительные параметры запроса:
    • addOnToken : токен авторизации, передаваемый в представление обнаружения вложений.
    • itemId : уникальный идентификатор CourseWork, CourseWorkMaterial или Announcement, который получает вложение надстройки.
    • itemType : либо «courseWork», «courseWorkMaterials», либо «announcement».
    • courseId : уникальный идентификатор курса Google Classroom, в котором создается задание.
    • attachmentId : уникальный идентификатор, присвоенный Google Classroom вложению дополнения после создания.
  • Реализуйте постоянное хранилище для вложений типа контента.
  • Предоставьте маршруты для создания вложений и обслуживания iframe в режиме «Представление учителя» и «Представление ученика».
  • Выполните следующие запросы к API надстроек Google Classroom:
    • Создайте новое вложение.
    • Получите контекст надстройки, который определяет, является ли вошедший в систему пользователь учащимся или преподавателем.

После завершения вы можете создавать вложения типа контента к заданиям через пользовательский интерфейс Google Classroom, войдя в систему как преподаватель. Преподаватели и учащиеся в классе также могут просматривать контент.

Включите API Класса

Выполняйте вызовы API Класса, начиная с этого шага. API должен быть включен для вашего проекта Google Cloud, прежде чем вы сможете совершать к нему вызовы. Перейдите к записи библиотеки API Google Classroom и выберите «Включить» .

Обработка параметров запроса представления обнаружения вложений

Как обсуждалось ранее , Google Classroom передает параметры запроса при загрузке представления обнаружения вложений в iframe:

  • courseId : идентификатор текущего курса Classroom.
  • itemId : уникальный идентификатор CourseWork, CourseWorkMaterial или Announcement, который получает вложение надстройки.
  • itemType : либо «courseWork», «courseWorkMaterials», либо «announcement».
  • addOnToken : токен, используемый для авторизации определенных действий надстройки Класса.
  • login_hint : идентификатор Google текущего пользователя.
  • hd : домен хоста текущего пользователя, например example.com .

В этом пошаговом руководстве рассматриваются courseId , itemId , itemType и addOnToken . Сохраняйте и передайте их при вызове API Класса.

Как и в предыдущем шаге пошагового руководства, сохраните переданные значения параметров запроса в нашем сеансе. Важно сделать это при первом открытии представления «Обнаружение вложений», поскольку это единственная возможность для Класса передать эти параметры запроса.

Питон

Перейдите к файлу сервера Flask, который предоставляет маршруты для представления обнаружения вложений ( attachment-discovery-routes.py , если вы следуете нашему примеру). В верхней части целевого маршрута дополнения ( /classroom-addon в нашем примере) получите и сохраните параметры запроса courseId , itemId , itemType и 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")

Записывайте эти значения в сеанс, только если они присутствуют; они не передаются снова, если пользователь позже вернется к представлению обнаружения вложений, не закрывая iframe.

Добавьте постоянное хранилище для вложений типа контента.

Вам нужна локальная запись всех созданных вложений. Это позволяет вам искать контент, выбранный учителем, по идентификаторам, предоставленным Классом.

Настройте схему базы данных для Attachment . В приведенном примере представлены вложения с изображением и подписью. Attachment содержит следующие атрибуты:

  • attachment_id : уникальный идентификатор вложения. Назначается Классом и возвращается в ответе при создании вложения.
  • image_filename : локальное имя файла отображаемого изображения.
  • image_caption : подпись, которая будет отображаться вместе с изображением.

Питон

Расширьте реализацию SQLite и flask_sqlalchemy из предыдущих шагов.

Перейдите к файлу, в котором вы определили свою таблицу пользователей ( models.py , если вы следуете приведенному нами примеру). Добавьте следующее внизу файла под классом 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))

Импортируйте новый класс Attachment в файл сервера с маршрутами обработки вложений.

Настройте новые маршруты

Начните этот шаг пошагового руководства с настройки нескольких новых страниц в нашем приложении. Они позволяют пользователю создавать и просматривать контент с помощью нашего дополнения.

Добавить маршруты создания вложений

Вам нужны страницы, на которых учитель может выбирать контент и отправлять запросы на создание вложений. Реализуйте маршрут /attachment-options , чтобы отображать параметры контента, которые учитель может выбрать. Также вам потребуются шаблоны страниц выбора контента и подтверждения создания. Предоставленные нами примеры содержат шаблоны для них, а также могут отображать запросы и ответы API Класса.

Обратите внимание, что вы также можете изменить существующую целевую страницу просмотра вложений для отображения параметров содержимого вместо создания новой страницы /attachment-options . Мы рекомендуем создать новую страницу для целей этого упражнения, чтобы сохранить поведение единого входа, реализованное на втором шаге пошагового руководства , например отзыв разрешений приложения. Они могут оказаться полезными при создании и тестировании дополнения.

Учитель может выбрать из небольшого набора изображений с подписями в нашем примере. Мы предоставили четыре изображения известных достопримечательностей, подписи к которым основаны на названиях файлов.

Питон

В нашем примере это находится в файле 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,
    )

Откроется страница «Создать вложения», похожая на следующую:

Пример представления выбора контента Python

Учитель может выбрать несколько изображений. Создайте по одному вложению для каждого изображения, выбранного учителем в методе create_attachments .

Отправка запросов на создание вложений

Теперь, когда вы знаете, какие фрагменты контента учитель хотел бы прикрепить, отправьте запросы API класса для создания вложений к нашему заданию. Сохраните данные вложения в своей базе данных после получения ответа от API Класса.

Начните с получения экземпляра службы Classroom:

Питон

В нашем примере это находится в файле 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)

Выполните запрос CREATE к конечной точке courses.courseWork.addOnAttachments . Для каждого изображения, выбранного преподавателем, сначала создайте объект AddOnAttachment :

Питон

В нашем примере это продолжение метода 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}",
    }

Для каждого вложения необходимо указать как минимум поля teacherViewUri , studentViewUri и title . teacherViewUri и studentViewUri представляют URL-адреса, которые загружаются при открытии вложения соответствующим типом пользователя.

Отправьте объект AddOnAttachment в теле запроса в соответствующую конечную точку addOnAttachments . Предоставляйте идентификаторы courseId , itemId , itemType и addOnToken с каждым запросом.

Питон

В нашем примере это продолжение метода 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()

Создайте запись для этого вложения в локальной базе данных, чтобы позже можно было загрузить правильный контент. Classroom возвращает уникальное значение id в ответ на запрос на создание, поэтому используйте его в качестве первичного ключа в нашей базе данных. Также обратите внимание, что Classroom передает параметр запроса attachmentId при открытии представлений «Учитель» и «Ученик».

Питон

В нашем примере это продолжение метода 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()

На этом этапе рассмотрите возможность перенаправления пользователя на страницу подтверждения, подтверждающего, что он успешно создал вложения.

Разрешить вложения из вашего дополнения

Сейчас самое время добавить все подходящие адреса в поле «Разрешенные префиксы URI вложений» на странице конфигурации приложения GWM SDK . Ваше дополнение может создавать вложения только с одним из префиксов URI, перечисленных на этой странице. Это мера безопасности, помогающая снизить вероятность атак «человек посередине».

Самый простой способ — указать в этом поле свой домен верхнего уровня, например https://example.com . https://localhost:<your port number>/ будет работать, если вы используете локальный компьютер в качестве веб-сервера.

Добавьте маршруты для представлений учителя и ученика.

Существует четыре iframe , в которые может быть загружено дополнение Google Classroom. На данный момент вы построили только маршруты, которые обслуживают iframe представления обнаружения вложений. Затем добавьте маршруты для обслуживания iframe в режиме просмотра учителя и ученика.

iframe «Представление учителя» необходим для предварительного просмотра опыта учащихся, но при желании может включать дополнительную информацию или функции редактирования.

Представление учащихся — это страница, которая отображается каждому учащемуся, когда он открывает вложение надстройки.

Для целей этого упражнения создайте один маршрут /load-content-attachment , который будет обслуживать представление как учителя, так и ученика. Используйте методы API Класса, чтобы определить, является ли пользователь учителем или учеником при загрузке страницы.

Питон

В нашем примере это находится в файле 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")

Имейте в виду, что на этом этапе вам также следует аутентифицировать пользователя. Здесь вам также следует обработать параметры запроса login_hint и hd и при необходимости направить пользователя к вашему потоку авторизации. Дополнительные сведения об этом процессе см. в инструкциях по входу в систему , обсуждавшихся в предыдущих пошаговых руководствах.

Затем отправьте запрос в конечную точку getAddOnContext , соответствующую типу элемента.

Питон

В нашем примере это продолжение метода 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()

Этот метод возвращает информацию о роли текущего пользователя в классе. Измените представление, представленное пользователю, в зависимости от его роли. В объекте ответа заполняется ровно одно из полей studentContext или teacherContext . Изучите их, чтобы определить, как обращаться к пользователю.

В любом случае используйте значение параметра запроса attachmentId , чтобы узнать, какое вложение получить из нашей базы данных. Этот параметр запроса предоставляется при открытии URI представления учителя или ученика.

Питон

В нашем примере это продолжение метода 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)

Протестируйте дополнение

Выполните следующие шаги, чтобы проверить создание вложения:

  • Войдите в [Google Classroom] как один из тестовых пользователей вашего учителя .
  • Перейдите на вкладку «Задания» и создайте новое задание .
  • Нажмите кнопку «Дополнения» под текстовой областью, затем выберите дополнение. Откроется iframe, и надстройка загрузит URI настройки вложения , который вы указали на странице конфигурации приложения GWM SDK.
  • Выберите фрагмент контента, который нужно прикрепить к заданию.
  • Закройте iframe после завершения процесса создания вложения.

Вы должны увидеть карточку вложения в пользовательском интерфейсе создания задания в Google Google Classroom. Щелкните карточку, чтобы открыть окно iframe «Представление учителя» и убедиться, что отображается правильное вложение. Нажмите кнопку «Назначить» .

Выполните следующие шаги, чтобы проверить опыт учащихся:

  • Затем войдите в Класс как пользователь-тестировщик учащегося в том же классе, что и пользователь-тестировщик-учитель.
  • Найдите тестовое задание на вкладке «Задания».
  • Разверните задание и щелкните карточку вложения, чтобы открыть iframe «Просмотр учащихся».

Убедитесь, что учащемуся отображается правильное вложение.

Поздравляем! Вы готовы перейти к следующему шагу: созданию вложений типа активности .