Added commands to manage classroom/course announcements.

This commit is contained in:
Ross Scroggs
2025-07-01 22:45:22 -07:00
parent 08f6f86d10
commit f87f000be2
4 changed files with 199 additions and 56 deletions

View File

@ -3120,8 +3120,15 @@ gam delete courses <CourseEntity> [archive|archived]
gam course <CourseID> create|add alias <CourseAlias> gam course <CourseID> create|add alias <CourseAlias>
gam course <CourseID> delete alias <CourseAlias> gam course <CourseID> delete alias <CourseAlias>
gam course <CourseID> create announcement
text <String> [scheduledtime <Time>] [state draft|published]
gam course <CourseID> remove announcement <CourseAnnouncementID>
gam course <CourseID> update announcement <CourseAnnouncementID>
[text <String>] [scheduledtime <Time>] [state published]
gam course <CourseID> create|add topic <CourseTopic> gam course <CourseID> create|add topic <CourseTopic>
gam course <CourseID> delete topic <CourseTopicID> gam course <CourseID> delete topic <CourseTopicID>
gam course <CourseID> update topic <CourseTopicID> <CourseTopic>
gam course <CourseID> create|add teachers [makefirstteacherowner] <UserItem> gam course <CourseID> create|add teachers [makefirstteacherowner] <UserItem>
gam course <CourseID> create|add students <UserItem> gam course <CourseID> create|add students <UserItem>
@ -3133,8 +3140,15 @@ gam course <CourseID> sync students [addonly|removeonly] <UserTypeEntity>
gam courses <CourseEntity> create|add alias <CourseAliasEntity> gam courses <CourseEntity> create|add alias <CourseAliasEntity>
gam courses <CourseEntity> delete alias <CourseAliasEntity> gam courses <CourseEntity> delete alias <CourseAliasEntity>
gam courses <CourseEntity> create announcement
text <String> [scheduledtime <Time>] [state draft|published]
gam courses <CourseEntity> remove announcement <CourseAnnouncementIDEntity>
gam courses <CourseEntity> update announcement <CourseAnnouncemntIDEntity>
[text <String>] [scheduledtime <Time>] [state published]
gam courses <CourseEntity> create|add topic <CourseTopicEntity> gam courses <CourseEntity> create|add topic <CourseTopicEntity>
gam courses <CourseEntity> delete topic <CourseTopicIDEntity> gam courses <CourseEntity> delete topic <CourseTopicIDEntity>
gam courses <CourseEntity> update topic <CourseTopicIDEntity> <CourseTopic>
gam courses <CourseEntity> create|add teachers [makefirstteacherowner] <UserTypeEntity> gam courses <CourseEntity> create|add teachers [makefirstteacherowner] <UserTypeEntity>
gam courses <CourseEntity> create|add students <UserTypeEntity> gam courses <CourseEntity> create|add students <UserTypeEntity>

View File

@ -1,8 +1,16 @@
7.11.00
Added commands to manage classroom/course announcements.
* See: https://github.com/GAM-team/GAM/wiki/Classroom-Courses#manage-course-announcements
Upgraded to OpenSSL 3.5.1.
7.10.10 7.10.10
Added choices `text` and `hyperlink` to option `showwebviewlink` in `gam [<UserTypeEntity>] print|show shareddrives`. Added choices `text` and `hyperlink` to option `showwebviewlink` in `gam [<UserTypeEntity>] print|show shareddrives`.
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>` * `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveNamw>") * `showwebviewlink hyperlink` - Displays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
7.10.09 7.10.09

View File

@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.10.10' __version__ = '7.11.00'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position #pylint: disable=wrong-import-position
@ -46776,6 +46776,13 @@ COURSE_STATE_MAPS = {
'published': 'PUBLISHED', 'published': 'PUBLISHED',
'deleted': 'DELETED', 'deleted': 'DELETED',
}, },
Cmd.OB_COURSE_ANNOUNCEMENT_ADD_STATE_LIST: {
'draft': 'DRAFT',
'published': 'PUBLISHED',
},
Cmd.OB_COURSE_ANNOUNCEMENT_UPDATE_STATE_LIST: {
'published': 'PUBLISHED',
},
Cmd.OB_COURSE_WORK_STATE_LIST: { Cmd.OB_COURSE_WORK_STATE_LIST: {
'draft': 'DRAFT', 'draft': 'DRAFT',
'published': 'PUBLISHED', 'published': 'PUBLISHED',
@ -48923,7 +48930,7 @@ def doPrintCourseParticipants():
csvPF.SetSortTitles(COURSE_PARTICIPANTS_SORT_TITLES) csvPF.SetSortTitles(COURSE_PARTICIPANTS_SORT_TITLES)
csvPF.writeCSVfile('Course Participants') csvPF.writeCSVfile('Course Participants')
def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role): def _batchAddItemsToCourse(croom, courseId, i, count, addItems, addType):
_ADD_PART_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST, _ADD_PART_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST,
GAPI.ALREADY_EXISTS: Msg.DUPLICATE, GAPI.ALREADY_EXISTS: Msg.DUPLICATE,
GAPI.FAILED_PRECONDITION: Msg.NOT_ALLOWED} GAPI.FAILED_PRECONDITION: Msg.NOT_ALLOWED}
@ -48939,7 +48946,7 @@ def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
else: else:
errMsg = getHTTPError(_ADD_PART_REASON_TO_MESSAGE_MAP, http_status, reason, message) errMsg = getHTTPError(_ADD_PART_REASON_TO_MESSAGE_MAP, http_status, reason, message)
if (reason == GAPI.PERMISSION_DENIED) and (ri[RI_ROLE] in {Ent.STUDENT, Ent.TEACHER}) and ('CannotDirectAddUser' in errMsg): if (reason == GAPI.PERMISSION_DENIED) and (ri[RI_ROLE] in {Ent.STUDENT, Ent.TEACHER}) and ('CannotDirectAddUser' in errMsg):
errMsg += f' Add external user with: gam user {ri[RI_ITEM]} create classroominvitation courses {ri[RI_ENTITY]} role {Ent.Singular(ri[RI_ROLE])}' errMsg += f' Add external user with: gam user {ri[RI_ITEM]} create classroominvitation courses {ri[RI_ENTITY]} addType {Ent.Singular(ri[RI_ROLE])}'
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], errMsg, int(ri[RI_J]), int(ri[RI_JCOUNT])) entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], errMsg, int(ri[RI_J]), int(ri[RI_JCOUNT]))
return return
waitOnFailure(1, 10, reason, message) waitOnFailure(1, 10, reason, message)
@ -48961,39 +48968,44 @@ def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e: except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT])) entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
if role == Ent.STUDENT: if addType == Ent.STUDENT:
service = croom.courses().students() service = croom.courses().students()
attribute = 'userId' attribute = 'userId'
elif role == Ent.TEACHER: elif addType == Ent.TEACHER:
service = croom.courses().teachers() service = croom.courses().teachers()
attribute = 'userId' attribute = 'userId'
elif role == Ent.COURSE_ALIAS: elif addType == Ent.COURSE_ALIAS:
service = croom.courses().aliases() service = croom.courses().aliases()
attribute = 'alias' attribute = 'alias'
else: # role == Ent.COURSE_TOPIC: elif addType == Ent.COURSE_TOPIC:
service = croom.courses().topics() service = croom.courses().topics()
attribute = 'name' attribute = 'name'
else: # addType == Ent.COURSE_ANNOUNCEMENT:
service = croom.courses().announcements()
attribute = 'text'
method = getattr(service, 'create') method = getattr(service, 'create')
Act.Set(Act.ADD) Act.Set(Act.ADD)
jcount = len(addParticipants) jcount = len(addItems)
noScopeCourseId = removeCourseIdScope(courseId) noScopeCourseId = removeCourseIdScope(courseId)
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, role, i, count) entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, addType, i, count)
Ind.Increment() Ind.Increment()
svcargs = dict([('courseId', courseId), ('body', {attribute: None}), ('fields', '')]+GM.Globals[GM.EXTRA_ARGS_LIST]) svcargs = dict([('courseId', courseId), ('body', {attribute: None}), ('fields', '')]+GM.Globals[GM.EXTRA_ARGS_LIST])
dbatch = croom.new_batch_http_request(callback=_callbackAddItemsToCourse) dbatch = croom.new_batch_http_request(callback=_callbackAddItemsToCourse)
bcount = 0 bcount = 0
j = 0 j = 0
for participant in addParticipants: for addItem in addItems:
j += 1 j += 1
svcparms = svcargs.copy() svcparms = svcargs.copy()
if role in {Ent.STUDENT, Ent.TEACHER}: if addType in {Ent.STUDENT, Ent.TEACHER}:
svcparms['body'][attribute] = cleanItem = normalizeEmailAddressOrUID(participant) svcparms['body'][attribute] = cleanItem = normalizeEmailAddressOrUID(addItem)
elif role == Ent.COURSE_ALIAS: elif addType == Ent.COURSE_ALIAS:
svcparms['body'][attribute] = addCourseAliasScope(participant) svcparms['body'][attribute] = addCourseAliasScope(addItem)
cleanItem = removeCourseAliasScope(svcparms['body'][attribute]) cleanItem = removeCourseAliasScope(svcparms['body'][attribute])
else: # role == Ent.COURSE_TOPIC: elif addType == Ent.COURSE_TOPIC:
svcparms['body'][attribute] = cleanItem = participant svcparms['body'][attribute] = cleanItem = addItem
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, role)) else: # addType == Ent.COURSE_ANNOUNCEMENT:
svcparms['body'] = cleanItem = addItem
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, addType))
bcount += 1 bcount += 1
if bcount >= GC.Values[GC.BATCH_SIZE]: if bcount >= GC.Values[GC.BATCH_SIZE]:
executeBatch(dbatch) executeBatch(dbatch)
@ -49003,7 +49015,7 @@ def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
dbatch.execute() dbatch.execute()
Ind.Decrement() Ind.Decrement()
def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, role): def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeItems, removeType):
_REMOVE_PART_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST, _REMOVE_PART_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST,
GAPI.FORBIDDEN: Msg.FORBIDDEN, GAPI.FORBIDDEN: Msg.FORBIDDEN,
GAPI.PERMISSION_DENIED: Msg.PERMISSION_DENIED} GAPI.PERMISSION_DENIED: Msg.PERMISSION_DENIED}
@ -49024,7 +49036,7 @@ def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, r
try: try:
callGAPI(service, 'delete', callGAPI(service, 'delete',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED,
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE], GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE, GAPI.FAILED_PRECONDITION],
retryReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE], triesLimit=0 if reason != GAPI.NOT_FOUND else 3, retryReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE], triesLimit=0 if reason != GAPI.NOT_FOUND else 3,
courseId=addCourseIdScope(ri[RI_ENTITY]), courseId=addCourseIdScope(ri[RI_ENTITY]),
body={attribute: ri[RI_ITEM] if ri[RI_ROLE] != Ent.COURSE_ALIAS else addCourseAliasScope(ri[RI_ITEM])}, body={attribute: ri[RI_ITEM] if ri[RI_ROLE] != Ent.COURSE_ALIAS else addCourseAliasScope(ri[RI_ITEM])},
@ -49035,42 +49047,47 @@ def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, r
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.FORBIDDEN, int(ri[RI_J]), int(ri[RI_JCOUNT])) entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.FORBIDDEN, int(ri[RI_J]), int(ri[RI_JCOUNT]))
except GAPI.permissionDenied: except GAPI.permissionDenied:
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.PERMISSION_DENIED, int(ri[RI_J]), int(ri[RI_JCOUNT])) entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.PERMISSION_DENIED, int(ri[RI_J]), int(ri[RI_JCOUNT]))
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e: except (GAPI.quotaExceeded, GAPI.serviceNotAvailable, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT])) entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
if role == Ent.STUDENT: if removeType == Ent.STUDENT:
service = croom.courses().students() service = croom.courses().students()
attribute = 'userId' attribute = 'userId'
elif role == Ent.TEACHER: elif removeType == Ent.TEACHER:
service = croom.courses().teachers() service = croom.courses().teachers()
attribute = 'userId' attribute = 'userId'
elif role == Ent.COURSE_ALIAS: elif removeType == Ent.COURSE_ALIAS:
service = croom.courses().aliases() service = croom.courses().aliases()
attribute = 'alias' attribute = 'alias'
else: # role == Ent.COURSE_TOPIC: elif removeType == Ent.COURSE_TOPIC:
service = croom.courses().topics() service = croom.courses().topics()
attribute = 'id' attribute = 'id'
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
service = croom.courses().announcements()
attribute = 'id'
method = getattr(service, 'delete') method = getattr(service, 'delete')
Act.Set(Act.REMOVE) Act.Set(Act.REMOVE)
jcount = len(removeParticipants) jcount = len(removeItems)
noScopeCourseId = removeCourseIdScope(courseId) noScopeCourseId = removeCourseIdScope(courseId)
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, role, i, count) entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, removeType, i, count)
Ind.Increment() Ind.Increment()
svcargs = dict([('courseId', courseId), ('fields', ''), (attribute, None)]+GM.Globals[GM.EXTRA_ARGS_LIST]) svcargs = dict([('courseId', courseId), ('fields', ''), (attribute, None)]+GM.Globals[GM.EXTRA_ARGS_LIST])
dbatch = croom.new_batch_http_request(callback=_callbackRemoveItemsFromCourse) dbatch = croom.new_batch_http_request(callback=_callbackRemoveItemsFromCourse)
bcount = 0 bcount = 0
j = 0 j = 0
for participant in removeParticipants: for removeItem in removeItems:
j += 1 j += 1
svcparms = svcargs.copy() svcparms = svcargs.copy()
if role in {Ent.STUDENT, Ent.TEACHER}: if removeType in {Ent.STUDENT, Ent.TEACHER}:
svcparms[attribute] = cleanItem = normalizeEmailAddressOrUID(participant) svcparms[attribute] = cleanItem = normalizeEmailAddressOrUID(removeItem)
elif role == Ent.COURSE_ALIAS: elif removeType == Ent.COURSE_ALIAS:
svcparms[attribute] = addCourseAliasScope(participant) svcparms[attribute] = addCourseAliasScope(removeItem)
cleanItem = removeCourseAliasScope(svcparms[attribute]) cleanItem = removeCourseAliasScope(svcparms[attribute])
else: # role == Ent.COURSE_TOPIC: elif removeType == Ent.COURSE_TOPIC:
svcparms[attribute] = cleanItem = participant svcparms[attribute] = cleanItem = removeItem
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, role)) else: # removeType == Ent.COURSE_ANNOUNCEMENT:
svcparms[attribute] = cleanItem = removeItem
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, removeType))
bcount += 1 bcount += 1
if bcount >= GC.Values[GC.BATCH_SIZE]: if bcount >= GC.Values[GC.BATCH_SIZE]:
executeBatch(dbatch) executeBatch(dbatch)
@ -49100,9 +49117,28 @@ def _updateCourseOwner(croom, courseId, owner, i, count):
entityActionPerformedMessage([Ent.COURSE, removeCourseIdScope(courseId), Ent.TEACHER, owner], Msg.ALREADY_WAS_OWNER, i, count) entityActionPerformedMessage([Ent.COURSE, removeCourseIdScope(courseId), Ent.TEACHER, owner], Msg.ALREADY_WAS_OWNER, i, count)
Act.Set(action) Act.Set(action)
ADD_REMOVE_PARTICIPANT_TYPES_MAP = { def getCourseAnnouncement(createCmd):
body = {}
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg == 'text':
body['text'] = getString(Cmd.OB_STRING, minLen=1, maxLen=30000)
elif myarg == 'scheduledtime':
body['scheduledTime'] = getTimeOrDeltaFromNow()
elif myarg == 'state':
body['state'] = getChoice(COURSE_STATE_MAPS[Cmd.OB_COURSE_ANNOUNCEMENT_ADD_STATE_LIST if createCmd else Cmd.OB_COURSE_ANNOUNCEMENT_UPDATE_STATE_LIST],
mapChoice=True)
else:
unknownArgumentExit()
if createCmd and 'text' not in body:
missingArgumentExit('text <String>')
return body
ADD_REMOVE_UPDATE_ITEM_TYPES_MAP = {
'alias': Ent.COURSE_ALIAS, 'alias': Ent.COURSE_ALIAS,
'aliases': Ent.COURSE_ALIAS, 'aliases': Ent.COURSE_ALIAS,
'announcement': Ent.COURSE_ANNOUNCEMENT,
'announcements': Ent.COURSE_ANNOUNCEMENT,
'student': Ent.STUDENT, 'student': Ent.STUDENT,
'students': Ent.STUDENT, 'students': Ent.STUDENT,
'teacher': Ent.TEACHER, 'teacher': Ent.TEACHER,
@ -49123,6 +49159,10 @@ PARTICIPANT_EN_MAP = {
# gam courses <CourseEntity> create alias <CourseAliasEntity> # gam courses <CourseEntity> create alias <CourseAliasEntity>
# gam course <CourseID> create alias <CourseAlias> # gam course <CourseID> create alias <CourseAlias>
# gam courses <CourseEntity> create announcement
# text <String> [scheduledtime <Time>] [state draft|published]
# gam course <CourseID> create announcement
# text <String> [scheduledtime <Time>] [state draft|published]
# gam courses <CourseEntity> create topic <CourseTopicEntity> # gam courses <CourseEntity> create topic <CourseTopicEntity>
# gam course <CourseID> create topic <CourseTopic> # gam course <CourseID> create topic <CourseTopic>
# gam courses <CourseEntity> create students <UserTypeEntity> # gam courses <CourseEntity> create students <UserTypeEntity>
@ -49131,35 +49171,39 @@ PARTICIPANT_EN_MAP = {
# gam course <CourseID> create teacher [makefirstteacherowner] <EmailAddress> # gam course <CourseID> create teacher [makefirstteacherowner] <EmailAddress>
def doCourseAddItems(courseIdList, getEntityListArg): def doCourseAddItems(courseIdList, getEntityListArg):
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
role = getChoice(ADD_REMOVE_PARTICIPANT_TYPES_MAP, mapChoice=True) addType = getChoice(ADD_REMOVE_UPDATE_ITEM_TYPES_MAP, mapChoice=True)
if role == Ent.TEACHER: if addType == Ent.TEACHER:
makeFirstTeacherOwner = checkArgumentPresent(['makefirstteacherowner']) makeFirstTeacherOwner = checkArgumentPresent(['makefirstteacherowner'])
else: else:
makeFirstTeacherOwner = False makeFirstTeacherOwner = False
if not getEntityListArg: if not getEntityListArg:
if role in {Ent.STUDENT, Ent.TEACHER}: if addType in {Ent.STUDENT, Ent.TEACHER}:
addItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS) addItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
elif role == Ent.COURSE_ALIAS: elif addType == Ent.COURSE_ALIAS:
addItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS) addItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS)
else: # role == Ent.COURSE_TOPIC: elif addType == Ent.COURSE_TOPIC:
addItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC) addItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC)
else: # addType == Ent.COURSE_ANNOUNCEMENT:
addItems = [getCourseAnnouncement(True)]
courseParticipantLists = None courseParticipantLists = None
else: else:
if role in {Ent.STUDENT, Ent.TEACHER}: if addType in {Ent.STUDENT, Ent.TEACHER}:
_, addItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS, _, addItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS,
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[role]}, typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[addType]},
isSuspended=False, isArchived=False) isSuspended=False, isArchived=False)
elif role == Ent.COURSE_ALIAS: elif addType == Ent.COURSE_ALIAS:
addItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True) addItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True)
else: # role == Ent.COURSE_TOPIC: elif addType == Ent.COURSE_TOPIC:
addItems = getEntityList(Cmd.OB_COURSE_TOPIC_ENTITY, shlexSplit=True) addItems = getEntityList(Cmd.OB_COURSE_TOPIC_ENTITY, shlexSplit=True)
else: # addType == Ent.COURSE_ANNOUNCEMENT:
addItems = getCourseAnnouncement(True)
courseParticipantLists = addItems if isinstance(addItems, dict) else None courseParticipantLists = addItems if isinstance(addItems, dict) else None
if courseParticipantLists is None: if courseParticipantLists is None:
firstTeacher = None firstTeacher = None
if makeFirstTeacherOwner and addItems: if makeFirstTeacherOwner and addItems:
firstTeacher = normalizeEmailAddressOrUID(addItems[0]) firstTeacher = normalizeEmailAddressOrUID(addItems[0])
checkForExtraneousArguments() checkForExtraneousArguments()
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, role == Ent.COURSE_TOPIC, i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, addType in {Ent.COURSE_TOPIC, Ent.COURSE_ANNOUNCEMENT},
addCIIdScope=courseParticipantLists is None) addCIIdScope=courseParticipantLists is None)
for courseId, courseInfo in coursesInfo.items(): for courseId, courseInfo in coursesInfo.items():
i += 1 i += 1
@ -49169,43 +49213,51 @@ def doCourseAddItems(courseIdList, getEntityListArg):
if makeFirstTeacherOwner and addItems: if makeFirstTeacherOwner and addItems:
firstTeacher = normalizeEmailAddressOrUID(addItems[0]) firstTeacher = normalizeEmailAddressOrUID(addItems[0])
courseId = addCourseIdScope(courseId) courseId = addCourseIdScope(courseId)
_batchAddItemsToCourse(courseInfo['croom'], courseId, i, count, addItems, role) _batchAddItemsToCourse(courseInfo['croom'], courseId, i, count, addItems, addType)
if makeFirstTeacherOwner and firstTeacher: if makeFirstTeacherOwner and firstTeacher:
_updateCourseOwner(courseInfo['croom'], courseId, firstTeacher, i, count) _updateCourseOwner(courseInfo['croom'], courseId, firstTeacher, i, count)
# gam courses <CourseEntity> remove alias <CourseAliasEntity> # gam courses <CourseEntity> remove alias <CourseAliasEntity>
# gam course <CourseID> remove alias <CourseAlias> # gam course <CourseID> remove alias <CourseAlias>
# gam courses <CourseEntity> remove announcement <CourseAnnouncementIDEntity>
# gam course <CourseID> remove announcement <CourseAnnouncementID>
# gam courses <CourseEntity> remove topic <CourseTopicIDEntity> # gam courses <CourseEntity> remove topic <CourseTopicIDEntity>
# gam course <CourseID> remove topic <CourseTopicID> # gam course <CourseID> remove topic <CourseTopicID>
# gam courses <CourseEntity> remove teachers|students [owneracccess] <UserTypeEntity> # gam courses <CourseEntity> remove teachers|students [owneracccess] <UserTypeEntity>
# gam course <CourseID> remove teacher|student [owneracccess] <EmailAddress> # gam course <CourseID> remove teacher|student [owneracccess] <EmailAddress>
def doCourseRemoveItems(courseIdList, getEntityListArg): def doCourseRemoveItems(courseIdList, getEntityListArg):
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
role = getChoice(ADD_REMOVE_PARTICIPANT_TYPES_MAP, mapChoice=True) removeType = getChoice(ADD_REMOVE_UPDATE_ITEM_TYPES_MAP, mapChoice=True)
if not getEntityListArg: if not getEntityListArg:
if role in {Ent.STUDENT, Ent.TEACHER}: if removeType in {Ent.STUDENT, Ent.TEACHER}:
useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS] useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
if checkArgumentPresent(OWNER_ACCESS_OPTIONS): if checkArgumentPresent(OWNER_ACCESS_OPTIONS):
useOwnerAccess = True useOwnerAccess = True
removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS) removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
elif role == Ent.COURSE_ALIAS: elif removeType == Ent.COURSE_ALIAS:
useOwnerAccess = False useOwnerAccess = False
removeItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS) removeItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS)
else: # role == Ent.COURSE_TOPIC: elif removeType == Ent.COURSE_TOPIC:
useOwnerAccess = True useOwnerAccess = True
removeItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID) removeItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID)
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
useOwnerAccess = True
removeItems = getStringReturnInList(Cmd.OB_COURSE_ANNOUNCEMENT_ID)
courseParticipantLists = None courseParticipantLists = None
else: else:
if role in {Ent.STUDENT, Ent.TEACHER}: if removeType in {Ent.STUDENT, Ent.TEACHER}:
useOwnerAccess = checkArgumentPresent(OWNER_ACCESS_OPTIONS) useOwnerAccess = checkArgumentPresent(OWNER_ACCESS_OPTIONS)
_, removeItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS, _, removeItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS,
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[role]}) typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[removeType]})
elif role == Ent.COURSE_ALIAS: elif removeType == Ent.COURSE_ALIAS:
useOwnerAccess = False useOwnerAccess = False
removeItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True) removeItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True)
else: # role == Ent.COURSE_TOPIC: elif removeType == Ent.COURSE_TOPIC:
useOwnerAccess = True useOwnerAccess = True
removeItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True) removeItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True)
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
useOwnerAccess = True
removeItems = getEntityList(Cmd.OB_COURSE_ANNOUNCEMENT_ID_ENTITY, shlexSplit=True)
courseParticipantLists = removeItems if isinstance(removeItems, dict) else None courseParticipantLists = removeItems if isinstance(removeItems, dict) else None
checkForExtraneousArguments() checkForExtraneousArguments()
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess, i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess,
@ -49215,7 +49267,72 @@ def doCourseRemoveItems(courseIdList, getEntityListArg):
if courseParticipantLists: if courseParticipantLists:
removeItems = courseParticipantLists[courseId] removeItems = courseParticipantLists[courseId]
courseId = addCourseIdScope(courseId) courseId = addCourseIdScope(courseId)
_batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeItems, role) _batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeItems, removeType)
# gam courses <CourseEntity> update announcement <CourseAnnouncemntIDEntity>
# [text <String>] [scheduledtime <Time>] [state published]
# gam course <CourseID> update announcement <CourseAnnouncementID>
# [text <String>] [scheduledtime <Time>] [state published]
# gam courses <CourseEntity> update topic <CourseTopicIDEntity> <CourseTopic>
# gam course <CourseID> update topic <CourseTopicID> <CourseTopic>
def doCourseUpdateItems(courseIdList, getEntityListArg):
croom = buildGAPIObject(API.CLASSROOM)
updateType = getChoice(ADD_REMOVE_UPDATE_ITEM_TYPES_MAP, mapChoice=True)
if not getEntityListArg:
if updateType == Ent.COURSE_TOPIC:
useOwnerAccess = True
updateItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID)
body = {'name': getString(Cmd.OB_COURSE_TOPIC)}
else: # updateType == Ent.COURSE_ANNOUNCEMENT:
useOwnerAccess = True
updateItems = getStringReturnInList(Cmd.OB_COURSE_ANNOUNCEMENT_ID)
body = getCourseAnnouncement(False)
courseItemLists = None
else:
if updateType == Ent.COURSE_TOPIC:
useOwnerAccess = True
updateItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True)
body = {'name': getString(Cmd.OB_COURSE_TOPIC)}
else: # updateType == Ent.COURSE_ANNOUNCEMENT:
useOwnerAccess = True
updateItems = getEntityList(Cmd.OB_COURSE_ANNOUNCEMENT_ID_ENTITY, shlexSplit=True)
body = getCourseAnnouncement(False)
courseItemLists = updateItems if isinstance(updateItems, dict) else None
checkForExtraneousArguments()
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess,
addCIIdScope=courseItemLists is None)
for courseId, courseInfo in coursesInfo.items():
i += 1
if courseItemLists:
updateItems = courseItemLists[courseId]
courseId = addCourseIdScope(courseId)
jcount = len(updateItems)
noScopeCourseId = removeCourseIdScope(courseId)
if updateType == Ent.COURSE_TOPIC:
service = courseInfo['croom'].courses().topics()
else: # updateType == Ent.COURSE_ANNOUNCEMENT:
service = courseInfo['croom'].courses().announcements()
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, updateType, i, count)
Ind.Increment()
j = 0
for updateItem in updateItems:
j += 1
try:
callGAPI(service, 'patch',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED,
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE],
courseId=addCourseIdScope(courseId), id=updateItem, updateMask=','.join(body.keys()), body=body, fields='')
entityActionPerformed([Ent.COURSE, courseId, updateType, updateItem], j, jcount)
except GAPI.notFound:
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], Msg.DOES_NOT_EXIST, j, jcount)
except GAPI.forbidden:
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], Msg.FORBIDDEN, j, jcount)
except GAPI.permissionDenied:
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], Msg.PERMISSION_DENIED, j, jcount)
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], str(e), j, jcount)
Ind.Decrement()
# gam courses <CourseEntity> clear teachers|students # gam courses <CourseEntity> clear teachers|students
# gam course <CourseID> clear teacher|student # gam course <CourseID> clear teacher|student
@ -77165,6 +77282,7 @@ COURSE_SUBCOMMANDS = {
'add': (Act.ADD, doCourseAddItems), 'add': (Act.ADD, doCourseAddItems),
'clear': (Act.REMOVE, doCourseClearParticipants), 'clear': (Act.REMOVE, doCourseClearParticipants),
'remove': (Act.REMOVE, doCourseRemoveItems), 'remove': (Act.REMOVE, doCourseRemoveItems),
'update': (Act.UPDATE, doCourseUpdateItems),
'sync': (Act.SYNC, doCourseSyncParticipants), 'sync': (Act.SYNC, doCourseSyncParticipants),
} }

View File

@ -875,8 +875,11 @@ class GamCLArgs():
OB_CONTACT_GROUP_ITEM = 'ContactGroupItem' OB_CONTACT_GROUP_ITEM = 'ContactGroupItem'
OB_COURSE_ALIAS = 'CourseAlias' OB_COURSE_ALIAS = 'CourseAlias'
OB_COURSE_ALIAS_ENTITY = 'CourseAliasEntity' OB_COURSE_ALIAS_ENTITY = 'CourseAliasEntity'
OB_COURSE_ANNOUNCEMENT_ID = "CourseAnnouncementID"
OB_COURSE_ANNOUNCEMENT_ID_ENTITY = "CourseAnnouncementIDEntity" OB_COURSE_ANNOUNCEMENT_ID_ENTITY = "CourseAnnouncementIDEntity"
OB_COURSE_ANNOUNCEMENT_STATE_LIST = "CourseAnnouncementStateList" OB_COURSE_ANNOUNCEMENT_STATE_LIST = "CourseAnnouncementStateList"
OB_COURSE_ANNOUNCEMENT_ADD_STATE_LIST = "CourseAnnouncementAddStateList"
OB_COURSE_ANNOUNCEMENT_UPDATE_STATE_LIST = "CourseAnnouncementUpdateStateList"
OB_COURSE_ENTITY = 'CourseEntity' OB_COURSE_ENTITY = 'CourseEntity'
OB_COURSE_ID = 'CourseID' OB_COURSE_ID = 'CourseID'
OB_COURSE_MATERIAL_ID_ENTITY = 'CourseMaterialIDEntity' OB_COURSE_MATERIAL_ID_ENTITY = 'CourseMaterialIDEntity'