첨부파일 성적 및 성적 패스백

이 둘러보기는 클래스룸 부가기능 둘러보기 시리즈의 여섯 번째 둘러보기입니다.

이 둘러보기에서는 이전 둘러보기 단계의 예시를 수정하여 채점된 활동 유형 첨부파일을 생성합니다. 또한 프로그래매틱 방식으로 성적을 Google 클래스룸에 다시 전달 합니다. 이 성적은 교사의 성적 기록에 초안 성적으로 표시됩니다.

이 둘러보기는 성적을 클래스룸에 다시 전달하는 두 가지 가능한 접근 방식이 있다는 점에서 시리즈의 다른 둘러보기와 약간 다릅니다. 두 가지 모두 개발자와 사용자 환경에 뚜렷한 영향을 미치므로 클래스룸 부가기능을 설계할 때 두 가지를 모두 고려하세요. 구현 옵션에 관한 추가 논의는 첨부파일과 상호작용 가이드 페이지 를 참고하세요.

API의 채점 기능은 선택사항 입니다. 모든 활동 유형 첨부파일 과 함께 사용할 수 있습니다.

이 둘러보기 과정에서 다음을 완료합니다.

  • 첨부파일의 성적 분모도 설정하도록 클래스룸 API에 대한 이전 첨부파일 생성 요청을 수정합니다.
  • 프로그래매틱 방식으로 학생의 제출물을 채점하고 첨부파일의 성적 분자를 설정합니다.
  • 로그인한 교사 사용자 인증 정보 또는 오프라인 교사 사용자 인증 정보를 사용하여 제출물의 성적을 클래스룸에 전달하는 두 가지 접근 방식을 구현합니다.

완료되면 패스백 동작이 트리거된 후 성적이 클래스룸 성적 기록에 표시됩니다. 이 시점은 구현 접근 방식에 따라 다릅니다.

이 예시에서는 학생에게 유명한 랜드마크 이미지가 표시되고 이름을 입력하라는 메시지가 표시되는 이전 둘러보기의 활동을 재사용합니다. 학생이 올바른 이름을 입력하면 첨부파일에 만점을 부여하고, 그렇지 않으면 0점을 부여합니다.

클래스룸 부가기능 API 채점 기능 이해

부가기능은 첨부파일의 성적 분자와 성적 분모를 모두 설정할 수 있습니다. 이러한 값은 API의 pointsEarnedmaxPoints 값을 사용하여 각각 설정됩니다. 클래스룸 UI의 첨부파일 카드에는 maxPoints 값이 설정된 경우 표시됩니다.

하나의 과제에 maxPoints가 있는 여러 첨부파일의 예

그림 1. maxPoints가 설정된 세 개의 부가기능 첨부파일 카드가 있는 과제 생성 UI

클래스룸 부가기능 API를 사용하면 첨부파일 성적에 대해 설정을 구성하고 획득한 점수를 설정할 수 있습니다. 이는 과제 성적과 동일하지 않습니다. 하지만 과제 성적 설정은 첨부파일 카드에 성적 동기화 라벨이 있는 첨부파일의 첨부파일 성적 설정을 따릅니다. '성적 동기화' 첨부파일이 학생 제출물의 pointsEarned를 설정하면 과제의 학생 초안 성적도 설정됩니다.

일반적으로 maxPoints를 설정하는 과제에 추가된 첫 번째 첨부파일에는 '성적 동기화' 라벨이 지정됩니다. '성적 동기화' 라벨의 예시는 그림 1에 표시된 과제 생성 UI 예시를 참고하세요. '첨부파일 1' 카드에 '성적 동기화' 라벨이 있고 빨간색 상자의 과제 성적이 50점으로 업데이트되었음을 확인하세요. 또한 그림 1에는 세 개의 첨부파일 카드가 표시되지만 하나의 카드에만 '성적 동기화' 라벨이 있습니다. 이는 현재 구현의 주요 제한사항입니다. 하나의 첨부파일에만 '성적 동기화' 라벨이 있을 수 있습니다.

`maxPoints`를 설정한 첨부파일이 여러 개 있는 경우 '성적 동기화'가 있는 첨부파일을 삭제해도 나머지 첨부파일에서 '성적 동기화'가 사용 설정되지 않습니다.maxPoints maxPoints를 설정하는 다른 첨부파일을 추가하면 새 첨부파일에서 성적 동기화가 사용 설정되고 최대 과제 성적이 이에 맞게 조정됩니다. 어떤 첨부파일에 '성적 동기화' 라벨이 있는지 또는 특정 과제에 첨부파일이 몇 개 있는지 프로그래매틱 방식으로 확인할 수 있는 메커니즘은 없습니다.

첨부파일의 최대 성적 설정

이 섹션에서는 첨부파일 성적의 분모 를 설정하는 방법을 설명합니다. 즉, 모든 학생이 제출물에 대해 달성할 수 있는 최대 점수입니다. 이렇게 하려면 첨부파일의 maxPoints 값을 설정합니다.

채점 기능을 사용 설정하려면 기존 구현을 약간만 수정하면 됩니다. 첨부파일을 만들 때 maxPoints 값을 AddOnAttachment 객체에 추가합니다. 이 객체에는 studentWorkReviewUri, teacherViewUri 및 기타 첨부파일 필드가 포함되어 있습니다.

새 과제의 기본 최대 점수는 100점입니다. 성적이 올바르게 설정되었는지 확인할 수 있도록 maxPoints를 100이 아닌 값으로 설정하는 것이 좋습니다. 데모로 maxPoints를 50으로 설정합니다.

Python

maxPoints 필드를 추가합니다. attachment 객체를 구성할 때 courses.courseWork.addOnAttachments 엔드포인트CREATE 요청을 보내기 직전에 제공된 예시를 따르는 경우 webapp/attachment_routes.py 파일에서 이 필드를 찾을 수 있습니다.

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}",
}

이 데모에서는 maxPoints 값을 로컬 첨부파일 데이터베이스에도 저장합니다. 이렇게 하면 나중에 학생 제출물을 채점할 때 추가 API 호출을 할 필요가 없습니다. 하지만 교사가 부가기능과 별개로 과제 성적 설정을 변경할 수 있습니다. GET 요청을 courses.courseWork 엔드포인트로 보내 과제 수준 maxPoints 값을 확인합니다. 이렇게 할 때는 CourseWork.id 필드에 itemId를 전달합니다.

이제 첨부파일의 maxPoints 값도 포함하도록 데이터베이스 모델을 업데이트합니다. CREATE 응답의 maxPoints 값을 사용하는 것이 좋습니다.

Python

먼저 Attachment 테이블에 max_points 필드를 추가합니다. 제공된 예시를 따르는 경우 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)

courses.courseWork.addOnAttachments CREATE 요청으로 돌아갑니다. 응답에서 반환된 maxPoints 값을 저장합니다.

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

이제 첨부파일에 최대 성적이 있습니다. 이제 이 동작을 테스트할 수 있습니다. 새 과제에 첨부파일을 추가하고 첨부파일 카드에 '성적 동기화' 라벨이 표시되고 과제의 '점수' 값이 변경되는지 확인합니다.

클래스룸에서 학생 제출물 성적 설정

이 섹션에서는 첨부파일 성적의 분자 를 설정하는 방법을 설명합니다. 즉, 첨부파일에 대한 개별 학생의 점수입니다. 이렇게 하려면 학생 첨부파일 제출물의 pointsEarned 값을 설정합니다.

이제 중요한 결정을 내려야 합니다. 부가기능에서 를 설정하는 요청을 어떻게 실행해야 할까요 pointsEarned?

문제는 pointsEarned 를 설정하려면 teacher OAuth 범위가 필요 하다는 것입니다. 학생 사용자에게 teacher 범위를 부여해서는 안 됩니다. 이렇게 하면 학생이 부가기능과 상호작용할 때 학생 뷰 iframe 대신 교사 뷰 iframe을 로드하는 등 예기치 않은 동작이 발생할 수 있습니다. 따라서 pointsEarned를 설정하는 방법에는 두 가지 선택사항이 있습니다.

  • 로그인한 교사의 사용자 인증 정보를 사용합니다.
  • 저장된 (오프라인) 교사 사용자 인증 정보를 사용합니다.

다음 섹션에서는 각 구현을 보여주기 전에 각 접근 방식의 장단점을 설명합니다. 제공된 예시에서는 성적을 클래스룸에 전달하는 두 가지 접근 방식을 모두 보여줍니다. 제공된 예시를 실행할 때 접근 방식을 선택하는 방법은 아래의 언어별 안내를 참고하세요.

Python

webapp/attachment_routes.py 파일 상단에서 SET_GRADE_WITH_LOGGED_IN_USER_CREDENTIALS 선언을 찾습니다. 로그인한 교사의 사용자 인증 정보를 사용하여 성적을 다시 전달하려면 이 값을 True로 설정합니다. 학생이 활동을 제출할 때 저장된 사용자 인증 정보를 사용하여 성적을 다시 전달하려면 이 값을 False로 설정합니다.

로그인한 교사의 사용자 인증 정보를 사용하여 성적 설정

로그인한 사용자의 사용자 인증 정보를 사용하여 pointsEarned를 설정하는 요청을 실행합니다. 지금까지의 나머지 구현을 반영하고 실현하는 데 거의 노력이 필요하지 않으므로 매우 직관적일 것입니다.

하지만 교사는 학생 작업 검토 iframe에서 학생의 제출물과 상호작용합니다. 여기에는 몇 가지 중요한 의미가 있습니다.

  • 교사가 클래스룸 UI에서 조치를 취할 때까지 클래스룸에 성적이 채워지지 않습니다.
  • 교사는 모든 학생 성적을 채우기 위해 모든 학생 제출물을 열어야 할 수 있습니다.
  • 클래스룸에서 성적을 수신한 후 클래스룸 UI에 표시되기까지 약간의 지연이 있습니다. 지연은 일반적으로 5~10초이지만 최대 30초까지 걸릴 수 있습니다.

이러한 요인이 결합되면 교사는 수업의 성적을 완전히 채우기 위해 상당한 시간과 수동 작업을 해야 할 수 있습니다.

이 접근 방식을 구현하려면 기존 학생 작업 검토 경로에 API 호출을 하나 더 추가합니다.

학생 제출물 및 첨부파일 기록을 가져온 후 학생의 제출물을 평가하고 결과 성적을 저장합니다. AddOnAttachmentStudentSubmission 객체pointsEarned 필드에 성적을 설정합니다. 마지막으로, PATCH 요청을 courses.courseWork.addOnAttachments.studentSubmissions 엔드포인트에 요청 본문에 AddOnAttachmentStudentSubmission 인스턴스와 함께 실행합니다. PATCH 요청의 updateMask에도 pointsEarned를 지정해야 합니다.

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

오프라인 교사 사용자 인증 정보를 사용하여 성적 설정

성적을 설정하는 두 번째 접근 방식에서는 첨부파일을 만든 교사의 저장된 사용자 인증 정보 를 사용해야 합니다. 이 구현에서는 이전에 승인된 교사의 갱신 토큰과 액세스 토큰을 사용하여 사용자 인증 정보를 구성한 다음 이러한 사용자 인증 정보를 사용하여 pointsEarned를 설정해야 합니다.

이 접근 방식의 중요한 이점은 클래스룸 UI에서 교사의 조치가 필요 없이 성적이 채워진다는 것입니다. 따라서 위에 언급된 문제를 피할 수 있습니다. 그 결과 최종 사용자는 채점 환경을 원활하고 효율적으로 인식합니다. 또한 이 접근 방식을 사용하면 학생이 활동을 완료하거나 비동기식으로 성적을 다시 전달하는 시점을 선택할 수 있습니다.

이 접근 방식을 구현하려면 다음 작업을 완료하세요.

  1. 액세스 토큰을 저장하도록 사용자 데이터베이스 기록을 수정합니다.
  2. 교사 ID를 저장하도록 첨부파일 데이터베이스 기록을 수정합니다.
  3. 교사의 사용자 인증 정보를 가져오고 필요에 따라 새 클래스룸 서비스 인스턴스를 구성합니다.
  4. 제출물의 성적을 설정합니다.

이 데모에서는 학생이 활동을 완료할 때, 즉 학생이 학생 뷰 경로에서 양식을 제출할 때 성적을 설정합니다.

액세스 토큰을 저장하도록 사용자 데이터베이스 기록 수정

API 호출을 하려면 두 개의 고유한 토큰, 즉 갱신 토큰액세스 토큰 이 필요합니다. 지금까지 둘러보기 시리즈를 따라왔다면 User 테이블 스키마에 이미 갱신 토큰이 저장되어 있어야 합니다. 인증 흐름의 일부로 액세스 토큰을 받으므로 로그인한 사용자로만 API 호출을 하는 경우 갱신 토큰을 저장하는 것으로 충분합니다.

하지만 이제 로그인한 사용자가 아닌 다른 사용자로 호출해야 하므로 인증 흐름을 사용할 수 없습니다. 따라서 갱신 토큰과 함께 액세스 토큰을 저장해야 합니다. 액세스 토큰을 포함하도록 User 테이블 스키마를 업데이트합니다.

Python

제공된 예시에서는 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())

그런 다음 액세스 토큰도 저장하도록 User 기록을 만들거나 업데이트하는 코드를 업데이트합니다.

Python

제공된 예시에서는 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()

교사 ID를 저장하도록 첨부파일 데이터베이스 기록 수정

활동의 성적을 설정하려면 과정의 교사로 pointsEarned를 설정하는 호출을 합니다. 이를 달성하는 방법에는 여러 가지가 있습니다.

  • 교사 사용자 인증 정보를 과정 ID에 매핑하는 로컬 매핑을 저장합니다. 하지만 동일한 교사가 항상 특정 과정과 연결되는 것은 아닙니다.
  • 클래스룸 API courses API 엔드포인트GET 요청을 실행하여 현재 교사를 가져옵니다. 그런 다음 로컬 사용자 기록을 쿼리하여 일치하는 교사 사용자 인증 정보를 찾습니다.
  • 부가기능 첨부파일을 만들 때 로컬 첨부파일 데이터베이스에 교사 ID를 저장합니다. 그런 다음 학생 뷰 iframe에 전달된 attachmentId에서 교사 사용자 인증 정보를 가져옵니다.

이 예시에서는 학생이 활동 첨부파일을 완료할 때 성적을 설정하므로 마지막 옵션을 보여줍니다.

데이터베이스의 Attachment 테이블에 교사 ID 필드를 추가합니다.

Python

제공된 예시에서는 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))

그런 다음 작성자의 ID도 저장하도록 Attachment 기록을 만들거나 업데이트하는 코드를 업데이트합니다.

Python

제공된 예시에서는 webapp/attachment_routes.py 파일의 create_attachments 메서드에 있습니다.

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

교사의 사용자 인증 정보 가져오기

학생 뷰 iframe을 제공하는 경로를 찾습니다. 학생의 응답을 로컬 데이터베이스에 저장한 직후 로컬 스토리지에서 교사의 사용자 인증 정보를 가져옵니다. 이전 두 단계에서 준비한 내용을 바탕으로 간단하게 수행할 수 있습니다. 이를 사용하여 교사 사용자의 클래스룸 서비스 새 인스턴스를 구성할 수도 있습니다.

Python

제공된 예시에서는 webapp/attachment_routes.py 파일의 load_activity_attachment 메서드에 있습니다.

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

제출물의 성적 설정

여기서부터의 절차는 로그인한 교사의 사용자 인증 정보를 사용하는 절차와 동일합니다. 하지만 이전 단계에서 가져온 교사 사용자 인증 정보로 호출해야 합니다.

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

부가기능 테스트

이전 둘러보기와 마찬가지로 교사로 활동 유형 첨부파일이 있는 과제를 만들고, 학생으로 응답을 제출한 다음, 학생 작업 검토 iframe에서 제출물을 엽니다. 구현 접근 방식에 따라 성적이 표시되는 시점이 다를 수 있습니다.

  • 학생이 활동을 완료할 때 성적을 다시 전달하도록 선택한 경우 학생 작업 검토 iframe을 열기 전에 UI에 초안 성적이 이미 표시되어 있어야 합니다. 과제를 열 때 학생 목록과 학생 작업 검토 iframe 옆의 '성적' 상자에서도 확인할 수 있습니다.
  • 교사가 학생 작업 검토 iframe을 열 때 성적을 다시 전달하도록 선택한 경우 iframe이 로드된 직후 '성적' 상자에 성적이 표시됩니다. 위에서 언급한 것처럼 최대 30초가 걸릴 수 있습니다. 그 후에는 다른 클래스룸 성적 기록 뷰에도 특정 학생의 성적이 표시됩니다.

학생에게 올바른 점수가 표시되는지 확인합니다.

축하합니다. 이제 다음 단계인 첨부파일 만들기 Google 클래스룸 외부로 진행할 수 있습니다.