ไฟล์แนบประเภทเนื้อหา

นี่เป็นคำแนะนำแบบทีละขั้นที่ 4 ในซีรีส์คำแนะนำแบบทีละขั้นเกี่ยวกับส่วนเสริมของ Classroom

ในคำแนะนำแบบทีละขั้นนี้ คุณจะโต้ตอบกับ Google Classroom API เพื่อสร้างไฟล์แนบ คุณระบุเส้นทางเพื่อให้ผู้ใช้ดูเนื้อหาไฟล์แนบได้ มุมมองจะแตกต่างกันไป ขึ้นอยู่กับบทบาทของผู้ใช้ในชั้นเรียน คำแนะนำแบบทีละขั้นนี้ครอบคลุมถึงไฟล์แนบประเภทเนื้อหา ซึ่งไม่จำเป็นต้องส่งงานของนักเรียน

ระหว่างคำแนะนำแบบทีละขั้นนี้ คุณได้ทำตามขั้นตอนต่อไปนี้

  • ดึงข้อมูลและใช้พารามิเตอร์การค้นหาของส่วนเสริมต่อไปนี้
    • addOnToken: โทเค็นการให้สิทธิ์ที่ส่งไปยังมุมมองการค้นพบไฟล์แนบ
    • itemId: ตัวระบุที่ไม่ซ้ำกันสำหรับ CourseWork, CourseWorkMaterial หรือประกาศที่ได้รับไฟล์แนบของส่วนเสริม
    • itemType: "courseWork", "courseWorkMaterials" หรือ "ประกาศ"
    • courseId: ตัวระบุที่ไม่ซ้ำกันสำหรับหลักสูตร Google Classroom ที่ใช้สร้างงาน
    • attachmentId: ตัวระบุที่ไม่ซ้ำกันซึ่ง Google Classroom กำหนดให้กับไฟล์แนบของส่วนเสริมหลังจากการสร้าง
  • ใช้พื้นที่เก็บข้อมูลถาวรสำหรับไฟล์แนบประเภทเนื้อหา
  • ระบุเส้นทางเพื่อสร้างไฟล์แนบและเพื่อแสดง iframe ในมุมมองของครูและมุมมองของนักเรียน
  • ส่งคำขอต่อไปนี้ไปยัง API ส่วนเสริมของ Google Classroom
    • สร้างไฟล์แนบใหม่
    • ดูบริบทส่วนเสริมที่ระบุว่าผู้ใช้ที่ลงชื่อเข้าสู่ระบบเป็นนักเรียนหรือครู

เมื่อเสร็จแล้ว คุณสามารถสร้างไฟล์แนบประเภทเนื้อหาในงานผ่าน UI ของ Google Classroom เมื่อเข้าสู่ระบบในฐานะครู นอกจากนี้ครูและนักเรียนในชั้นเรียน ยังดูเนื้อหาได้

เปิดใช้ Classroom API

เรียกใช้ Classroom API โดยเริ่มจากขั้นตอนนี้ คุณต้องเปิดใช้ API สำหรับโปรเจ็กต์ Google Cloud ก่อนจึงจะเรียกใช้ API ได้ ไปที่รายการไลบรารีของ Google Classroom API แล้วเลือกเปิดใช้

จัดการพารามิเตอร์การค้นหาของมุมมองการค้นพบไฟล์แนบ

อย่างที่กล่าวถึงก่อนหน้านี้ Google Classroom จะส่งพารามิเตอร์การค้นหาเมื่อโหลดมุมมองการค้นพบไฟล์แนบใน iframe ดังนี้

  • courseId: รหัสของหลักสูตร Classroom ปัจจุบัน
  • itemId: ตัวระบุที่ไม่ซ้ำกันสำหรับ CourseWork, CourseWorkMaterial หรือประกาศที่ได้รับไฟล์แนบของส่วนเสริม
  • itemType: "courseWork", "courseWorkMaterials" หรือ "ประกาศ"
  • addOnToken: โทเค็นที่ใช้ในการให้สิทธิ์การดำเนินการส่วนเสริมของ Classroom บางรายการ
  • login_hint: รหัส Google ของผู้ใช้ปัจจุบัน
  • hd: โดเมนโฮสต์สำหรับผู้ใช้ปัจจุบัน เช่น example.com

คำแนะนำแบบทีละขั้นนี้ครอบคลุมถึง courseId, itemId, itemType และ addOnToken เก็บรักษาและส่งต่อเมื่อเรียกใช้ Classroom API

ในคำแนะนำแบบทีละขั้น ให้จัดเก็บค่าพารามิเตอร์การค้นหาที่ส่งผ่านในเซสชันของเรา เราจำเป็นต้องดำเนินการเมื่อเปิดมุมมองการค้นพบไฟล์แนบเป็นครั้งแรก เนื่องจากนี่เป็นโอกาสเดียวที่ Classroom จะส่งผ่านพารามิเตอร์การค้นหาเหล่านี้

Python

ไปยังไฟล์เซิร์ฟเวอร์ Flask ที่มีเส้นทางสำหรับมุมมองการค้นพบของไฟล์แนบ (attachment-discovery-routes.py หากคุณทำตามตัวอย่างที่ให้ไว้) ที่ด้านบนของเส้นทาง Landing Page ของส่วนเสริม (/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

เพิ่มพื้นที่เก็บข้อมูลถาวรสำหรับไฟล์แนบประเภทเนื้อหา

คุณต้องมีระเบียนไฟล์แนบที่สร้างขึ้นในเครื่อง ซึ่งจะช่วยให้คุณค้นหาเนื้อหาที่ครูเลือกได้โดยใช้ตัวระบุที่ Classroom ให้ไว้

ตั้งค่าสคีมาฐานข้อมูลสำหรับ Attachment ตัวอย่างที่เราให้มามี ไฟล์แนบที่แสดงรูปภาพและคำบรรยายภาพ Attachment มีแอตทริบิวต์ต่อไปนี้

  • attachment_id: ตัวระบุที่ไม่ซ้ำกันสำหรับไฟล์แนบ Classroom มอบหมายและส่งคืนในคำตอบเมื่อสร้างไฟล์แนบ
  • image_filename: ชื่อไฟล์ในเครื่องของรูปภาพที่จะแสดง
  • image_caption: คำบรรยายที่จะแสดงพร้อมรูปภาพ

Python

ขยายการใช้งาน 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-options เพื่อแสดงตัวเลือกเนื้อหาให้ครูเลือก คุณต้องมีเทมเพลตสำหรับหน้าการเลือกเนื้อหา และการยืนยันการสร้างด้วย ตัวอย่างที่เราให้ไว้มีเทมเพลตสำหรับแสดงคำขอและคำตอบจาก Classroom API ด้วย

โปรดทราบว่าคุณสามารถแก้ไขหน้า Landing Page ของมุมมองการค้นพบไฟล์แนบที่มีอยู่เพื่อแสดงตัวเลือกเนื้อหาแทนการสร้างหน้า /attachment-options ใหม่ได้ เราขอแนะนำให้สร้างหน้าใหม่สำหรับแบบฝึกหัดนี้เพื่อให้ยังคงลักษณะการทํางานของ SSO ที่ใช้ในขั้นตอนที่ 2 ของขั้นตอนที่ 2 เช่น การเพิกถอนสิทธิ์ของแอป ขั้นตอนเหล่านี้น่าจะมีประโยชน์เมื่อคุณสร้างและทดสอบส่วนเสริม

ครูจะเลือกได้จากชุดรูปภาพที่มีคำบรรยายชุดเล็กๆ ในตัวอย่างที่เราให้ไว้ เราได้จัดเตรียมภาพ 4 ภาพของสถานที่สำคัญที่มีชื่อเสียง ซึ่งมีคำบรรยายที่ดึงมาจากชื่อไฟล์

Python

ในตัวอย่างที่เราให้ไว้ ไฟล์นี้อยู่ในไฟล์ 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

ครูจะเลือกรูปภาพได้หลายรูป สร้างไฟล์แนบ 1 ไฟล์สำหรับแต่ละรูปภาพ ที่ครูเลือกไว้ในเมธอด create_attachments

คำขอสร้างไฟล์แนบปัญหา

เมื่อทราบแล้วว่าครูต้องการแนบเนื้อหาส่วนใดบ้าง ให้ออกคำขอไปยัง Classroom API เพื่อสร้างไฟล์แนบในงานของเรา จัดเก็บรายละเอียดไฟล์แนบในฐานข้อมูลหลังจากได้รับการตอบกลับจาก Classroom API

เริ่มต้นด้วยการรับอินสแตนซ์ของบริการ Classroom ดังนี้

Python

ในตัวอย่างที่เราให้ไว้ ไฟล์นี้อยู่ในไฟล์ 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 ก่อน โดยทำดังนี้

Python

ในตัวอย่างที่เราให้ไว้ นี่คือความต่อเนื่องของเมธอด 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 ในคำขอแต่ละรายการ

Python

ในตัวอย่างที่เราให้ไว้ นี่คือความต่อเนื่องของเมธอด 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 เมื่อเปิดมุมมองสำหรับครูและนักเรียน ดังนี้

Python

ในตัวอย่างที่เราให้ไว้ นี่คือความต่อเนื่องของเมธอด 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 4 รายการที่โหลดส่วนเสริมของ Google Classroom ได้ ในขณะนี้ คุณได้สร้างเฉพาะเส้นทางที่แสดง iframe มุมมองการค้นพบไฟล์แนบ ถัดไป ให้เพิ่มเส้นทางเพื่อแสดง iframe ในมุมมองของครูและนักเรียนด้วย

ต้องมี iframe มุมมองของครูเพื่อแสดงตัวอย่างประสบการณ์การใช้งานของนักเรียน แต่อาจรวมถึงข้อมูลเพิ่มเติมหรือฟีเจอร์แต่งภาพด้วย

มุมมองของนักเรียน คือหน้าที่แสดงให้กับนักเรียนแต่ละคนเมื่อเปิดไฟล์แนบของส่วนเสริม

สำหรับวัตถุประสงค์ของแบบฝึกหัดนี้ ให้สร้างเส้นทาง /load-content-attachment เส้นทางเดียวที่ให้บริการทั้งมุมมองของครูและนักเรียน ใช้เมธอด Classroom API เพื่อระบุว่าผู้ใช้เป็นครูหรือนักเรียนเมื่อโหลดหน้าเว็บ

Python

ในตัวอย่างที่เราให้ไว้ ไฟล์นี้อยู่ในไฟล์ 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 ที่ตรงกับประเภทรายการ

Python

ในตัวอย่างที่เราให้ไว้ นี่คือความต่อเนื่องของเมธอด 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 มุมมองของครูหรือนักเรียน

Python

ในตัวอย่างที่เราให้ไว้ นี่คือความต่อเนื่องของเมธอด 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 หลังจากขั้นตอนการสร้างไฟล์แนบเสร็จสมบูรณ์แล้ว

คุณจะเห็นการ์ดไฟล์แนบปรากฏใน UI การสร้างงานใน Google Google Classroom คลิกการ์ดเพื่อเปิด iframe มุมมองสำหรับครู แล้วตรวจสอบว่าไฟล์แนบที่ถูกต้องปรากฏขึ้น คลิกปุ่มมอบหมาย

ทำตามขั้นตอนต่อไปนี้เพื่อทดสอบประสบการณ์ของนักเรียน

  • จากนั้นลงชื่อเข้าใช้ Classroom ในฐานะผู้ใช้แบบทดสอบของนักเรียนในชั้นเรียนเดียวกันกับผู้ใช้แบบทดสอบสำหรับครู
  • ค้นหางานทดสอบในแท็บงานของชั้นเรียน
  • ขยายงานแล้วคลิกการ์ดไฟล์แนบเพื่อเปิด iframe มุมมองของนักเรียน

ยืนยันว่าไฟล์แนบที่ถูกต้องจะปรากฏต่อนักเรียน

ยินดีด้วย คุณพร้อมไปยังขั้นตอนถัดไปแล้ว นั่นคือการสร้างไฟล์แนบประเภทกิจกรรม