mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-04 04:41:35 +00:00
Compare commits
9 Commits
v7.10.09
...
20250703.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b218abaae7 | ||
|
|
967898fa86 | ||
|
|
80570f2fda | ||
|
|
6437547e33 | ||
|
|
f87f000be2 | ||
|
|
08f6f86d10 | ||
|
|
e70bfca92a | ||
|
|
2be5d40f44 | ||
|
|
170e188f1f |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -126,7 +126,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
cache.tar.xz
|
cache.tar.xz
|
||||||
key: gam-${{ matrix.jid }}-20250611
|
key: gam-${{ matrix.jid }}-20250701
|
||||||
|
|
||||||
- name: Untar Cache archive
|
- name: Untar Cache archive
|
||||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
his document describes the GAM command line syntax in modified BNF, see https://en.wikipedia.org/wiki/Backus-Naur_Form
|
This document describes the GAM command line syntax in modified BNF, see https://en.wikipedia.org/wiki/Backus-Naur_Form
|
||||||
Skip the History section and start reading at Introduction.
|
Skip the History section and start reading at Introduction.
|
||||||
|
|
||||||
Items on the command line are space separated, when an actual space character is required, it will be indicated by <Space>.
|
Items on the command line are space separated, when an actual space character is required, it will be indicated by <Space>.
|
||||||
@@ -405,6 +405,11 @@ If an item contains spaces, it should be surrounded by ".
|
|||||||
<ContactGroupItem> ::= <ContactGroupID>|<ContactGroupName>
|
<ContactGroupItem> ::= <ContactGroupID>|<ContactGroupName>
|
||||||
<CorporaAttribute> ::= alldrives|allteamdrives|domain|onlyteamdrives|user
|
<CorporaAttribute> ::= alldrives|allteamdrives|domain|onlyteamdrives|user
|
||||||
<CourseAlias> ::= <String>
|
<CourseAlias> ::= <String>
|
||||||
|
<CourseAnnouncementContent> ::=
|
||||||
|
((text <String>)|
|
||||||
|
(textfile <FileName> [charset <Charset>])|
|
||||||
|
(gdoc <UserGoogleDoc>)|
|
||||||
|
(gcsdoc <StorageBucketObjectName>))
|
||||||
<CourseAnnouncementID> ::= <Number>
|
<CourseAnnouncementID> ::= <Number>
|
||||||
<CourseAnnouncementState> ::= draft|published|deleted
|
<CourseAnnouncementState> ::= draft|published|deleted
|
||||||
<CourseID> ::= <Number>|d:<CourseAlias>
|
<CourseID> ::= <Number>|d:<CourseAlias>
|
||||||
@@ -3120,8 +3125,21 @@ 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>
|
||||||
|
|
||||||
|
<CourseAnnouncementContent> ::=
|
||||||
|
((text <String>)|
|
||||||
|
(textfile <FileName> [charset <Charset>])|
|
||||||
|
(gdoc <UserGoogleDoc>)|
|
||||||
|
(gcsdoc <StorageBucketObjectName>))
|
||||||
|
|
||||||
|
gam course <CourseID> create announcement
|
||||||
|
<CourseAnnouncementContent> [scheduledtime <Time>] [state draft|published]
|
||||||
|
gam course <CourseID> remove announcement <CourseAnnouncementID>
|
||||||
|
gam course <CourseID> update announcement <CourseAnnouncementID>
|
||||||
|
[<CourseAnnouncementContent>] [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 +3151,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
|
||||||
|
<CourseAnnouncementContent>> [scheduledtime <Time>] [state draft|published]
|
||||||
|
gam courses <CourseEntity> remove announcement <CourseAnnouncementIDEntity>
|
||||||
|
gam courses <CourseEntity> update announcement <CourseAnnouncementIDEntity>
|
||||||
|
[<CourseAnnouncementContent>] [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>
|
||||||
@@ -4817,13 +4842,13 @@ gam print shareddrives [todrive <ToDriveAttribute>*]
|
|||||||
[teamdriveadminquery|query <QueryTeamDrive>]
|
[teamdriveadminquery|query <QueryTeamDrive>]
|
||||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
[fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
[fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[formatjson [quotechar <Character>]]
|
[formatjson [quotechar <Character>]]
|
||||||
gam show shareddrives
|
gam show shareddrives
|
||||||
[teamdriveadminquery|query <QueryTeamDrive>]
|
[teamdriveadminquery|query <QueryTeamDrive>]
|
||||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
[fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
[fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[formatjson] [noorgunits [<Boolean>]]
|
[formatjson] [noorgunits [<Boolean>]]
|
||||||
|
|
||||||
gam print shareddriveorganizers [todrive <ToDriveAttribute>*]
|
gam print shareddriveorganizers [todrive <ToDriveAttribute>*]
|
||||||
@@ -4904,14 +4929,14 @@ gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
|||||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
(role|roles <SharedDriveACLRoleList>)*
|
(role|roles <SharedDriveACLRoleList>)*
|
||||||
[fields <SharedDriveFieldNameList>]
|
[fields <SharedDriveFieldNameList>]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
[guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
||||||
gam <UserTypeEntity> show shareddrives
|
gam <UserTypeEntity> show shareddrives
|
||||||
[teamdriveadminquery|query <QueryTeamDrive>]
|
[teamdriveadminquery|query <QueryTeamDrive>]
|
||||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
(role|roles <SharedDriveACLRoleList>)*
|
(role|roles <SharedDriveACLRoleList>)*
|
||||||
[fields <SharedDriveFieldNameList>]
|
[fields <SharedDriveFieldNameList>]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[guiroles [<Boolean>]] [formatjson]
|
[guiroles [<Boolean>]] [formatjson]
|
||||||
|
|
||||||
<PermissionMatch> ::=
|
<PermissionMatch> ::=
|
||||||
|
|||||||
@@ -1,3 +1,29 @@
|
|||||||
|
7.11.01
|
||||||
|
|
||||||
|
Updated `gam course <CourseID> create|update announcement` to accept input from
|
||||||
|
a literal string, a file or a Google Doc.
|
||||||
|
```
|
||||||
|
<CourseAnnouncementContent> ::=
|
||||||
|
((text <String>)|
|
||||||
|
(textfile <FileName> [charset <Charset>])|
|
||||||
|
(gdoc <UserGoogleDoc>)|
|
||||||
|
(gcsdoc <StorageBucketObjectName>))
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 hyperlink` - Displays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||||
|
|
||||||
7.10.09
|
7.10.09
|
||||||
|
|
||||||
Added option `showwebviewlink` to `gam [<UserTypeEntity>] print|show shareddrives` that
|
Added option `showwebviewlink` to `gam [<UserTypeEntity>] print|show shareddrives` that
|
||||||
|
|||||||
@@ -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.09'
|
__version__ = '7.11.01'
|
||||||
__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
|
||||||
@@ -634,7 +634,8 @@ def accessErrorMessage(cd, errMsg=None):
|
|||||||
cd = buildGAPIObject(API.DIRECTORY)
|
cd = buildGAPIObject(API.DIRECTORY)
|
||||||
try:
|
try:
|
||||||
callGAPI(cd.customers(), 'get',
|
callGAPI(cd.customers(), 'get',
|
||||||
throwReasons=[GAPI.BAD_REQUEST, GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.BAD_REQUEST, GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customerKey=GC.Values[GC.CUSTOMER_ID], fields='id')
|
customerKey=GC.Values[GC.CUSTOMER_ID], fields='id')
|
||||||
except (GAPI.badRequest, GAPI.invalidInput):
|
except (GAPI.badRequest, GAPI.invalidInput):
|
||||||
return formatKeyValueList('',
|
return formatKeyValueList('',
|
||||||
@@ -671,14 +672,16 @@ def accessErrorExitNonDirectory(api, errMsg):
|
|||||||
''))
|
''))
|
||||||
|
|
||||||
def ClientAPIAccessDeniedExit(errMsg=None):
|
def ClientAPIAccessDeniedExit(errMsg=None):
|
||||||
|
if errMsg is None:
|
||||||
stderrErrorMsg(Msg.API_ACCESS_DENIED)
|
stderrErrorMsg(Msg.API_ACCESS_DENIED)
|
||||||
if errMsg:
|
|
||||||
stderrErrorMsg(errMsg)
|
|
||||||
missingScopes = API.getClientScopesSet(GM.Globals[GM.CURRENT_CLIENT_API])-GM.Globals[GM.CURRENT_CLIENT_API_SCOPES]
|
missingScopes = API.getClientScopesSet(GM.Globals[GM.CURRENT_CLIENT_API])-GM.Globals[GM.CURRENT_CLIENT_API_SCOPES]
|
||||||
if missingScopes:
|
if missingScopes:
|
||||||
writeStderr(Msg.API_CHECK_CLIENT_AUTHORIZATION.format(GM.Globals[GM.OAUTH2_CLIENT_ID],
|
writeStderr(Msg.API_CHECK_CLIENT_AUTHORIZATION.format(GM.Globals[GM.OAUTH2_CLIENT_ID],
|
||||||
','.join(sorted(missingScopes))))
|
','.join(sorted(missingScopes))))
|
||||||
systemErrorExit(API_ACCESS_DENIED_RC, None)
|
systemErrorExit(API_ACCESS_DENIED_RC, None)
|
||||||
|
else:
|
||||||
|
stderrErrorMsg(errMsg)
|
||||||
|
systemErrorExit(API_ACCESS_DENIED_RC, Msg.REAUTHENTICATION_IS_NEEDED)
|
||||||
|
|
||||||
def SvcAcctAPIAccessDenied():
|
def SvcAcctAPIAccessDenied():
|
||||||
_getSvcAcctData()
|
_getSvcAcctData()
|
||||||
@@ -4458,6 +4461,8 @@ def handleOAuthTokenError(e, softErrors, displayError=False, i=0, count=0):
|
|||||||
errMsg.startswith('invalid_request: Invalid impersonation "sub" field')):
|
errMsg.startswith('invalid_request: Invalid impersonation "sub" field')):
|
||||||
if not GM.Globals[GM.CURRENT_SVCACCT_USER]:
|
if not GM.Globals[GM.CURRENT_SVCACCT_USER]:
|
||||||
ClientAPIAccessDeniedExit()
|
ClientAPIAccessDeniedExit()
|
||||||
|
# 403 Forbidden, API disabled, user not enabled
|
||||||
|
# 400 Bad Request, user not defined
|
||||||
if softErrors:
|
if softErrors:
|
||||||
entityActionFailedWarning([Ent.USER, GM.Globals[GM.CURRENT_SVCACCT_USER], Ent.USER, None], errMsg, i, count)
|
entityActionFailedWarning([Ent.USER, GM.Globals[GM.CURRENT_SVCACCT_USER], Ent.USER, None], errMsg, i, count)
|
||||||
return None
|
return None
|
||||||
@@ -4465,6 +4470,7 @@ def handleOAuthTokenError(e, softErrors, displayError=False, i=0, count=0):
|
|||||||
if errMsg in API.OAUTH2_UNAUTHORIZED_ERRORS:
|
if errMsg in API.OAUTH2_UNAUTHORIZED_ERRORS:
|
||||||
if not GM.Globals[GM.CURRENT_SVCACCT_USER]:
|
if not GM.Globals[GM.CURRENT_SVCACCT_USER]:
|
||||||
ClientAPIAccessDeniedExit()
|
ClientAPIAccessDeniedExit()
|
||||||
|
# 401 Unauthorized, API disabled, user enabled
|
||||||
if softErrors:
|
if softErrors:
|
||||||
if displayError:
|
if displayError:
|
||||||
apiOrScopes = API.getAPIName(GM.Globals[GM.CURRENT_SVCACCT_API]) if GM.Globals[GM.CURRENT_SVCACCT_API] else ','.join(sorted(GM.Globals[GM.CURRENT_SVCACCT_API_SCOPES]))
|
apiOrScopes = API.getAPIName(GM.Globals[GM.CURRENT_SVCACCT_API]) if GM.Globals[GM.CURRENT_SVCACCT_API] else ','.join(sorted(GM.Globals[GM.CURRENT_SVCACCT_API_SCOPES]))
|
||||||
@@ -6515,7 +6521,8 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
|
|||||||
printGettingAllEntityItemsForWhom(Ent.TEACHER, removeCourseIdScope(courseId), entityType=Ent.COURSE)
|
printGettingAllEntityItemsForWhom(Ent.TEACHER, removeCourseIdScope(courseId), entityType=Ent.COURSE)
|
||||||
result = callGAPIpages(courseInfo['croom'].courses().teachers(), 'list', 'teachers',
|
result = callGAPIpages(courseInfo['croom'].courses().teachers(), 'list', 'teachers',
|
||||||
pageMessage=getPageMessageForWhom(),
|
pageMessage=getPageMessageForWhom(),
|
||||||
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE],
|
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||||
courseId=courseId, fields='nextPageToken,teachers/profile/emailAddress',
|
courseId=courseId, fields='nextPageToken,teachers/profile/emailAddress',
|
||||||
pageSize=GC.Values[GC.CLASSROOM_MAX_RESULTS])
|
pageSize=GC.Values[GC.CLASSROOM_MAX_RESULTS])
|
||||||
@@ -6528,7 +6535,8 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
|
|||||||
printGettingAllEntityItemsForWhom(Ent.STUDENT, removeCourseIdScope(courseId), entityType=Ent.COURSE)
|
printGettingAllEntityItemsForWhom(Ent.STUDENT, removeCourseIdScope(courseId), entityType=Ent.COURSE)
|
||||||
result = callGAPIpages(courseInfo['croom'].courses().students(), 'list', 'students',
|
result = callGAPIpages(courseInfo['croom'].courses().students(), 'list', 'students',
|
||||||
pageMessage=getPageMessageForWhom(),
|
pageMessage=getPageMessageForWhom(),
|
||||||
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE],
|
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||||
courseId=courseId, fields='nextPageToken,students/profile/emailAddress',
|
courseId=courseId, fields='nextPageToken,students/profile/emailAddress',
|
||||||
pageSize=GC.Values[GC.CLASSROOM_MAX_RESULTS])
|
pageSize=GC.Values[GC.CLASSROOM_MAX_RESULTS])
|
||||||
@@ -6544,8 +6552,8 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
|
|||||||
entityActionNotPerformedWarning([Ent.COURSE, removeCourseIdScope(courseId)], str(e))
|
entityActionNotPerformedWarning([Ent.COURSE, removeCourseIdScope(courseId)], str(e))
|
||||||
GM.Globals[GM.CLASSROOM_SERVICE_NOT_AVAILABLE] = True
|
GM.Globals[GM.CLASSROOM_SERVICE_NOT_AVAILABLE] = True
|
||||||
break
|
break
|
||||||
except (GAPI.forbidden, GAPI.badRequest):
|
except (GAPI.forbidden, GAPI.permissionDenied, GAPI.badRequest) as e:
|
||||||
ClientAPIAccessDeniedExit()
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
elif entityType == Cmd.ENTITY_CROS:
|
elif entityType == Cmd.ENTITY_CROS:
|
||||||
buildGAPIObject(API.DIRECTORY)
|
buildGAPIObject(API.DIRECTORY)
|
||||||
result = convertEntityToList(entity)
|
result = convertEntityToList(entity)
|
||||||
@@ -9023,7 +9031,7 @@ class CSVPrintFile():
|
|||||||
normalizeSortHeaders()
|
normalizeSortHeaders()
|
||||||
if self.outputTranspose:
|
if self.outputTranspose:
|
||||||
newRows = []
|
newRows = []
|
||||||
newTitlesList = [i for i in range(len(self.rows)+1)]
|
newTitlesList = list(range(len(self.rows) + 1))
|
||||||
for title in titlesList:
|
for title in titlesList:
|
||||||
i = 0
|
i = 0
|
||||||
newRow = {i: title}
|
newRow = {i: title}
|
||||||
@@ -16010,8 +16018,9 @@ def doCreateDomainAlias():
|
|||||||
checkForExtraneousArguments()
|
checkForExtraneousArguments()
|
||||||
try:
|
try:
|
||||||
callGAPI(cd.domainAliases(), 'insert',
|
callGAPI(cd.domainAliases(), 'insert',
|
||||||
throwReasons=[GAPI.DOMAIN_NOT_FOUND, GAPI.DUPLICATE, GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
throwReasons=[GAPI.DOMAIN_NOT_FOUND, GAPI.DUPLICATE, GAPI.INVALID, GAPI.CONFLICT,
|
||||||
GAPI.FORBIDDEN, GAPI.CONFLICT],
|
GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
|
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
|
||||||
entityActionPerformed([Ent.DOMAIN, body['parentDomainName'], Ent.DOMAIN_ALIAS, body['domainAliasName']])
|
entityActionPerformed([Ent.DOMAIN, body['parentDomainName'], Ent.DOMAIN_ALIAS, body['domainAliasName']])
|
||||||
except GAPI.domainNotFound:
|
except GAPI.domainNotFound:
|
||||||
@@ -16020,8 +16029,10 @@ def doCreateDomainAlias():
|
|||||||
entityActionFailedWarning([Ent.DOMAIN, body['parentDomainName'], Ent.DOMAIN_ALIAS, body['domainAliasName']], Msg.DUPLICATE)
|
entityActionFailedWarning([Ent.DOMAIN, body['parentDomainName'], Ent.DOMAIN_ALIAS, body['domainAliasName']], Msg.DUPLICATE)
|
||||||
except (GAPI.invalid, GAPI.conflict) as e:
|
except (GAPI.invalid, GAPI.conflict) as e:
|
||||||
entityActionFailedWarning([Ent.DOMAIN, body['parentDomainName'], Ent.DOMAIN_ALIAS, body['domainAliasName']], str(e))
|
entityActionFailedWarning([Ent.DOMAIN, body['parentDomainName'], Ent.DOMAIN_ALIAS, body['domainAliasName']], str(e))
|
||||||
except (GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e:
|
except (GAPI.badRequest, GAPI.notFound) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
|
|
||||||
# gam delete domainalias|aliasdomain <DomainAlias>
|
# gam delete domainalias|aliasdomain <DomainAlias>
|
||||||
def doDeleteDomainAlias():
|
def doDeleteDomainAlias():
|
||||||
@@ -16030,13 +16041,16 @@ def doDeleteDomainAlias():
|
|||||||
checkForExtraneousArguments()
|
checkForExtraneousArguments()
|
||||||
try:
|
try:
|
||||||
callGAPI(cd.domainAliases(), 'delete',
|
callGAPI(cd.domainAliases(), 'delete',
|
||||||
throwReasons=[GAPI.DOMAIN_ALIAS_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.DOMAIN_ALIAS_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID], domainAliasName=domainAliasName)
|
customer=GC.Values[GC.CUSTOMER_ID], domainAliasName=domainAliasName)
|
||||||
entityActionPerformed([Ent.DOMAIN_ALIAS, domainAliasName])
|
entityActionPerformed([Ent.DOMAIN_ALIAS, domainAliasName])
|
||||||
except GAPI.domainAliasNotFound:
|
except GAPI.domainAliasNotFound:
|
||||||
entityActionFailedWarning([Ent.DOMAIN_ALIAS, domainAliasName], Msg.DOES_NOT_EXIST)
|
entityActionFailedWarning([Ent.DOMAIN_ALIAS, domainAliasName], Msg.DOES_NOT_EXIST)
|
||||||
except (GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e:
|
except (GAPI.badRequest, GAPI.notFound) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
|
|
||||||
DOMAIN_TIME_OBJECTS = {'creationTime'}
|
DOMAIN_TIME_OBJECTS = {'creationTime'}
|
||||||
DOMAIN_ALIAS_PRINT_ORDER = ['parentDomainName', 'creationTime', 'verified']
|
DOMAIN_ALIAS_PRINT_ORDER = ['parentDomainName', 'creationTime', 'verified']
|
||||||
@@ -16064,14 +16078,17 @@ def doInfoDomainAlias():
|
|||||||
FJQC = FormatJSONQuoteChar(formatJSONOnly=True)
|
FJQC = FormatJSONQuoteChar(formatJSONOnly=True)
|
||||||
try:
|
try:
|
||||||
result = callGAPI(cd.domainAliases(), 'get',
|
result = callGAPI(cd.domainAliases(), 'get',
|
||||||
throwReasons=[GAPI.DOMAIN_ALIAS_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.DOMAIN_ALIAS_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID], domainAliasName=domainAliasName)
|
customer=GC.Values[GC.CUSTOMER_ID], domainAliasName=domainAliasName)
|
||||||
aliasSkipObjects = DOMAIN_ALIAS_SKIP_OBJECTS
|
aliasSkipObjects = DOMAIN_ALIAS_SKIP_OBJECTS
|
||||||
_showDomainAlias(result, FJQC, aliasSkipObjects)
|
_showDomainAlias(result, FJQC, aliasSkipObjects)
|
||||||
except GAPI.domainAliasNotFound:
|
except GAPI.domainAliasNotFound:
|
||||||
entityActionFailedWarning([Ent.DOMAIN_ALIAS, domainAliasName], Msg.DOES_NOT_EXIST)
|
entityActionFailedWarning([Ent.DOMAIN_ALIAS, domainAliasName], Msg.DOES_NOT_EXIST)
|
||||||
except (GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e:
|
except (GAPI.badRequest, GAPI.notFound) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
|
|
||||||
def _printDomain(domain, csvPF):
|
def _printDomain(domain, csvPF):
|
||||||
row = {}
|
row = {}
|
||||||
@@ -16107,7 +16124,8 @@ def doPrintShowDomainAliases():
|
|||||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||||
try:
|
try:
|
||||||
domainAliases = callGAPIitems(cd.domainAliases(), 'list', 'domainAliases',
|
domainAliases = callGAPIitems(cd.domainAliases(), 'list', 'domainAliases',
|
||||||
throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID])
|
customer=GC.Values[GC.CUSTOMER_ID])
|
||||||
count = len(domainAliases)
|
count = len(domainAliases)
|
||||||
if showItemCountOnly:
|
if showItemCountOnly:
|
||||||
@@ -16125,8 +16143,10 @@ def doPrintShowDomainAliases():
|
|||||||
csvPF.WriteRowNoFilter({'domainAliasName': domainAlias['domainAliasName'],
|
csvPF.WriteRowNoFilter({'domainAliasName': domainAlias['domainAliasName'],
|
||||||
'JSON': json.dumps(cleanJSON(domainAlias, timeObjects=DOMAIN_TIME_OBJECTS),
|
'JSON': json.dumps(cleanJSON(domainAlias, timeObjects=DOMAIN_TIME_OBJECTS),
|
||||||
ensure_ascii=False, sort_keys=True)})
|
ensure_ascii=False, sort_keys=True)})
|
||||||
except (GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e:
|
except (GAPI.badRequest, GAPI.notFound) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
if csvPF:
|
if csvPF:
|
||||||
csvPF.writeCSVfile('Domain Aliases')
|
csvPF.writeCSVfile('Domain Aliases')
|
||||||
|
|
||||||
@@ -16137,15 +16157,19 @@ def doCreateDomain():
|
|||||||
checkForExtraneousArguments()
|
checkForExtraneousArguments()
|
||||||
try:
|
try:
|
||||||
callGAPI(cd.domains(), 'insert',
|
callGAPI(cd.domains(), 'insert',
|
||||||
throwReasons=[GAPI.DUPLICATE, GAPI.DOMAIN_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.CONFLICT],
|
throwReasons=[GAPI.DUPLICATE, GAPI.CONFLICT,
|
||||||
|
GAPI.DOMAIN_NOT_FOUND, GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
|
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
|
||||||
entityActionPerformed([Ent.DOMAIN, body['domainName']])
|
entityActionPerformed([Ent.DOMAIN, body['domainName']])
|
||||||
except GAPI.duplicate:
|
except GAPI.duplicate:
|
||||||
entityDuplicateWarning([Ent.DOMAIN, body['domainName']])
|
entityDuplicateWarning([Ent.DOMAIN, body['domainName']])
|
||||||
except GAPI.conflict as e:
|
except GAPI.conflict as e:
|
||||||
entityActionFailedWarning([Ent.DOMAIN, body['domainName']], str(e))
|
entityActionFailedWarning([Ent.DOMAIN, body['domainName']], str(e))
|
||||||
except (GAPI.domainNotFound, GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e:
|
except (GAPI.domainNotFound, GAPI.badRequest, GAPI.notFound) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
|
|
||||||
# gam update domain <DomainName> primary
|
# gam update domain <DomainName> primary
|
||||||
def doUpdateDomain():
|
def doUpdateDomain():
|
||||||
@@ -16162,13 +16186,17 @@ def doUpdateDomain():
|
|||||||
missingArgumentExit('primary')
|
missingArgumentExit('primary')
|
||||||
try:
|
try:
|
||||||
callGAPI(cd.customers(), 'update',
|
callGAPI(cd.customers(), 'update',
|
||||||
throwReasons=[GAPI.DOMAIN_NOT_VERIFIED_SECONDARY, GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_INPUT],
|
throwReasons=[GAPI.DOMAIN_NOT_VERIFIED_SECONDARY, GAPI.BAD_REQUEST,
|
||||||
|
GAPI.RESOURCE_NOT_FOUND, GAPI.INVALID_INPUT,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customerKey=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
|
customerKey=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
|
||||||
entityActionPerformedMessage([Ent.DOMAIN, domainName], Msg.NOW_THE_PRIMARY_DOMAIN)
|
entityActionPerformedMessage([Ent.DOMAIN, domainName], Msg.NOW_THE_PRIMARY_DOMAIN)
|
||||||
except GAPI.domainNotVerifiedSecondary:
|
except GAPI.domainNotVerifiedSecondary:
|
||||||
entityActionFailedWarning([Ent.DOMAIN, domainName], Msg.DOMAIN_NOT_VERIFIED_SECONDARY)
|
entityActionFailedWarning([Ent.DOMAIN, domainName], Msg.DOMAIN_NOT_VERIFIED_SECONDARY)
|
||||||
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.invalidInput) as e:
|
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.invalidInput) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
|
|
||||||
# gam delete domain <DomainName>
|
# gam delete domain <DomainName>
|
||||||
def doDeleteDomain():
|
def doDeleteDomain():
|
||||||
@@ -16177,11 +16205,14 @@ def doDeleteDomain():
|
|||||||
checkForExtraneousArguments()
|
checkForExtraneousArguments()
|
||||||
try:
|
try:
|
||||||
callGAPI(cd.domains(), 'delete',
|
callGAPI(cd.domains(), 'delete',
|
||||||
throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID], domainName=domainName)
|
customer=GC.Values[GC.CUSTOMER_ID], domainName=domainName)
|
||||||
entityActionPerformed([Ent.DOMAIN, domainName])
|
entityActionPerformed([Ent.DOMAIN, domainName])
|
||||||
except (GAPI.badRequest, GAPI.notFound, GAPI.forbidden) as e:
|
except (GAPI.badRequest, GAPI.notFound) as e:
|
||||||
accessErrorExit(cd, str(e))
|
accessErrorExit(cd, str(e))
|
||||||
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
|
|
||||||
CUSTOMER_LICENSE_MAP = {
|
CUSTOMER_LICENSE_MAP = {
|
||||||
'accounts:num_users': 'Total Users',
|
'accounts:num_users': 'Total Users',
|
||||||
@@ -16210,7 +16241,7 @@ def _showCustomerLicenseInfo(customerInfo, FJQC):
|
|||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
result = callGAPI(rep.customerUsageReports(), 'get',
|
result = callGAPI(rep.customerUsageReports(), 'get',
|
||||||
throwReasons=[GAPI.INVALID, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.INVALID, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
date=tryDate, customerId=customerInfo['id'],
|
date=tryDate, customerId=customerInfo['id'],
|
||||||
fields='warnings,usageReports', parameters=parameters)
|
fields='warnings,usageReports', parameters=parameters)
|
||||||
usageReports = numUsersAvailable(result)
|
usageReports = numUsersAvailable(result)
|
||||||
@@ -16228,8 +16259,8 @@ def _showCustomerLicenseInfo(customerInfo, FJQC):
|
|||||||
if not tryDate:
|
if not tryDate:
|
||||||
return
|
return
|
||||||
continue
|
continue
|
||||||
except GAPI.forbidden:
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
return
|
ClientAPIAccessDeniedExit(str(e))
|
||||||
if not FJQC.formatJSON:
|
if not FJQC.formatJSON:
|
||||||
printKeyValueList([f'User counts as of {tryDate}:'])
|
printKeyValueList([f'User counts as of {tryDate}:'])
|
||||||
Ind.Increment()
|
Ind.Increment()
|
||||||
@@ -46776,6 +46807,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',
|
||||||
@@ -47681,7 +47719,8 @@ def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess, addCIIdScope=True):
|
|||||||
if courseId not in coursesInfo:
|
if courseId not in coursesInfo:
|
||||||
try:
|
try:
|
||||||
course = callGAPI(croom.courses(), 'get',
|
course = callGAPI(croom.courses(), 'get',
|
||||||
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE],
|
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
|
||||||
|
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||||
id=courseId, fields='name,ownerId')
|
id=courseId, fields='name,ownerId')
|
||||||
if useOwnerAccess:
|
if useOwnerAccess:
|
||||||
@@ -47692,9 +47731,9 @@ def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess, addCIIdScope=True):
|
|||||||
coursesInfo[ciCourseId] = {'name': course['name'], 'croom': ocroom}
|
coursesInfo[ciCourseId] = {'name': course['name'], 'croom': ocroom}
|
||||||
except GAPI.notFound:
|
except GAPI.notFound:
|
||||||
entityDoesNotExistWarning(Ent.COURSE, courseId)
|
entityDoesNotExistWarning(Ent.COURSE, courseId)
|
||||||
except (GAPI.permissionDenied, GAPI.serviceNotAvailable) as e:
|
except GAPI.serviceNotAvailable as e:
|
||||||
entityActionFailedWarning([Ent.COURSE, courseId], str(e))
|
entityActionFailedWarning([Ent.COURSE, courseId], str(e))
|
||||||
except GAPI.forbidden:
|
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
ClientAPIAccessDeniedExit()
|
ClientAPIAccessDeniedExit()
|
||||||
return 0, len(coursesInfo), coursesInfo
|
return 0, len(coursesInfo), coursesInfo
|
||||||
|
|
||||||
@@ -48923,77 +48962,114 @@ 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):
|
||||||
|
def _addIdToResponse(response, riItem):
|
||||||
|
if addType == Ent.COURSE_ANNOUNCEMENT:
|
||||||
|
respId = response.get('id', '')
|
||||||
|
elif addType == Ent.COURSE_TOPIC:
|
||||||
|
respId = response.get('topicId', '')
|
||||||
|
else:
|
||||||
|
respId = ''
|
||||||
|
if respId:
|
||||||
|
return riItem + f'({respId})'
|
||||||
|
return riItem
|
||||||
|
|
||||||
_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}
|
||||||
def _callbackAddItemsToCourse(request_id, _, exception):
|
def _callbackAddItemsToCourse(request_id, response, exception):
|
||||||
ri = request_id.splitlines()
|
ri = request_id.splitlines()
|
||||||
|
if addType == Ent.COURSE_ANNOUNCEMENT:
|
||||||
|
mg = re.match(r"^{'text': '(.+)'}$", ri[RI_ITEM])
|
||||||
|
if mg:
|
||||||
|
riText = mg.group(1)
|
||||||
|
else:
|
||||||
|
riText = ''
|
||||||
|
if len(riText) > 100:
|
||||||
|
riItem = riText[0:100]+'...'
|
||||||
|
else:
|
||||||
|
riItem = riText
|
||||||
|
else:
|
||||||
|
riItem = ri[RI_ITEM]
|
||||||
if exception is None:
|
if exception is None:
|
||||||
entityActionPerformed([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
riItem = _addIdToResponse(response, riItem)
|
||||||
|
entityActionPerformed([Ent.COURSE, ri[RI_ENTITY], addType, riItem], int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
else:
|
else:
|
||||||
http_status, reason, message = checkGAPIError(exception)
|
http_status, reason, message = checkGAPIError(exception)
|
||||||
if (reason not in {GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE}) and ((reason != GAPI.NOT_FOUND) or (ri[RI_ROLE] == Ent.COURSE_ALIAS)):
|
if (reason not in {GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE}) and ((reason != GAPI.NOT_FOUND) or (addType == Ent.COURSE_ALIAS)):
|
||||||
if reason in [GAPI.FORBIDDEN, GAPI.BACKEND_ERROR]:
|
if reason in [GAPI.FORBIDDEN, GAPI.BACKEND_ERROR]:
|
||||||
errMsg = getPhraseDNEorSNA(ri[RI_ITEM])
|
errMsg = getPhraseDNEorSNA(riItem)
|
||||||
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 (addType 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 {riItem} create classroominvitation courses {ri[RI_ENTITY]} addType {Ent.Singular(addType)}'
|
||||||
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], addType, riItem], errMsg, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
return
|
return
|
||||||
waitOnFailure(1, 10, reason, message)
|
waitOnFailure(1, 10, reason, message)
|
||||||
|
if addType in {Ent.STUDENT, Ent.TEACHER, Ent.COURSE_TOPIC}:
|
||||||
|
rbody = {attribute: riItem}
|
||||||
|
elif addType == Ent.COURSE_ALIAS:
|
||||||
|
rbody = {attribute: addCourseAliasScope(riItem)}
|
||||||
|
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||||
|
rbody = ri[RI_ITEM]
|
||||||
try:
|
try:
|
||||||
callGAPI(service, 'create',
|
result = callGAPI(service, 'create',
|
||||||
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BACKEND_ERROR,
|
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BACKEND_ERROR,
|
||||||
GAPI.ALREADY_EXISTS, GAPI.FAILED_PRECONDITION,
|
GAPI.ALREADY_EXISTS, GAPI.FAILED_PRECONDITION,
|
||||||
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE],
|
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE],
|
||||||
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=rbody, fields=returnFields)
|
||||||
body={attribute: ri[RI_ITEM] if ri[RI_ROLE] != Ent.COURSE_ALIAS else addCourseAliasScope(ri[RI_ITEM])},
|
riItem = _addIdToResponse(result, riItem)
|
||||||
fields='')
|
|
||||||
except (GAPI.notFound, GAPI.backendError, GAPI.forbidden):
|
except (GAPI.notFound, GAPI.backendError, GAPI.forbidden):
|
||||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], getPhraseDNEorSNA(ri[RI_ITEM]), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], addType, riItem], getPhraseDNEorSNA(riItem), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
except GAPI.alreadyExists:
|
except GAPI.alreadyExists:
|
||||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.DUPLICATE, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], addType, riItem], Msg.DUPLICATE, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
except GAPI.failedPrecondition:
|
except GAPI.failedPrecondition:
|
||||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.NOT_ALLOWED, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], addType, riItem], Msg.NOT_ALLOWED, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
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], addType, riItem], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
|
|
||||||
if role == Ent.STUDENT:
|
returnFields = ''
|
||||||
|
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'
|
||||||
|
returnFields = 'topicId'
|
||||||
|
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||||
|
service = croom.courses().announcements()
|
||||||
|
attribute = 'text'
|
||||||
|
returnFields = 'id'
|
||||||
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', returnFields)]+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,74 +49079,82 @@ 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}
|
||||||
def _callbackRemoveItemsFromCourse(request_id, _, exception):
|
def _callbackRemoveItemsFromCourse(request_id, _, exception):
|
||||||
ri = request_id.splitlines()
|
ri = request_id.splitlines()
|
||||||
|
riItem = ri[RI_ITEM]
|
||||||
if exception is None:
|
if exception is None:
|
||||||
entityActionPerformed([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
entityActionPerformed([Ent.COURSE, ri[RI_ENTITY], removeType, riItem], int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
else:
|
else:
|
||||||
http_status, reason, message = checkGAPIError(exception)
|
http_status, reason, message = checkGAPIError(exception)
|
||||||
if reason not in {GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE}:
|
if reason not in {GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE}:
|
||||||
if reason == GAPI.NOT_FOUND and ri[RI_ROLE] != Ent.COURSE_ALIAS:
|
if reason == GAPI.NOT_FOUND and removeType != Ent.COURSE_ALIAS:
|
||||||
errMsg = f'{Msg.NOT_A} {Ent.Singular(ri[RI_ROLE])}'
|
errMsg = f'{Msg.NOT_A} {Ent.Singular(removeType)}'
|
||||||
else:
|
else:
|
||||||
errMsg = getHTTPError(_REMOVE_PART_REASON_TO_MESSAGE_MAP, http_status, reason, message)
|
errMsg = getHTTPError(_REMOVE_PART_REASON_TO_MESSAGE_MAP, http_status, reason, message)
|
||||||
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], removeType, riItem], errMsg, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
return
|
return
|
||||||
waitOnFailure(1, 10, reason, message)
|
waitOnFailure(1, 10, reason, message)
|
||||||
|
if removeType in {Ent.STUDENT, Ent.TEACHER, Ent.COURSE_TOPIC, Ent.COURSE_ANNOUNCEMENT}:
|
||||||
|
rbody = {attribute: riItem}
|
||||||
|
else: # removeType == Ent.COURSE_ALIAS:
|
||||||
|
rbody = {attribute: addCourseAliasScope(riItem)}
|
||||||
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=rbody, fields='')
|
||||||
body={attribute: ri[RI_ITEM] if ri[RI_ROLE] != Ent.COURSE_ALIAS else addCourseAliasScope(ri[RI_ITEM])},
|
|
||||||
fields='')
|
|
||||||
except GAPI.notFound:
|
except GAPI.notFound:
|
||||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.DOES_NOT_EXIST, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], removeType, riItem], Msg.DOES_NOT_EXIST, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||||
except GAPI.forbidden:
|
except GAPI.forbidden:
|
||||||
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], removeType, riItem], 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], removeType, riItem], 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], removeType, riItem], 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 +49184,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 in SORF_TEXT_ARGUMENTS:
|
||||||
|
body['text'] = getStringOrFile(myarg, minLen=1, unescapeCRLF=True)[0]
|
||||||
|
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 +49226,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
|
||||||
|
# <CourseAnnouncementContent> [scheduledtime <Time>] [state draft|published]
|
||||||
|
# gam course <CourseID> create announcement
|
||||||
|
# <CourseAnnouncementContent> [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 +49238,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 +49280,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 +49334,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>
|
||||||
|
# [<CourseAnnouncementContent>] [scheduledtime <Time>] [state published]
|
||||||
|
# gam course <CourseID> update announcement <CourseAnnouncementID>
|
||||||
|
# [<CourseAnnouncementContent>] [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
|
||||||
@@ -65870,12 +66054,14 @@ SHAREDDRIVE_ACL_ROLES_MAP = {
|
|||||||
'writer': 'writer',
|
'writer': 'writer',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SHOWWEBVIEWLINK_CHOICES = {'text', 'hyperlink'}
|
||||||
|
|
||||||
# gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
# gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
||||||
# [asadmin [shareddriveadminquery|query <QuerySharedDrive>]]
|
# [asadmin [shareddriveadminquery|query <QuerySharedDrive>]]
|
||||||
# [matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
# [matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
# (role|roles <SharedDriveACLRoleList>)*
|
# (role|roles <SharedDriveACLRoleList>)*
|
||||||
# [fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
# [fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||||
# [showwebviewlink]
|
# [showwebviewlink [text|hyperlink]]
|
||||||
# [guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
# [guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
||||||
# [showitemcountonly]
|
# [showitemcountonly]
|
||||||
# gam <UserTypeEntity> show shareddrives
|
# gam <UserTypeEntity> show shareddrives
|
||||||
@@ -65883,7 +66069,7 @@ SHAREDDRIVE_ACL_ROLES_MAP = {
|
|||||||
# [matchname <REMatchPattrn>] [orgunit|org|ou <OrgUnitPath>]
|
# [matchname <REMatchPattrn>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
# (role|roles <SharedDriveACLRoleLIst>)*
|
# (role|roles <SharedDriveACLRoleLIst>)*
|
||||||
# [fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
# [fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||||
# [showwebviewlink]
|
# [showwebviewlink [text|hyperlink]]
|
||||||
# [guiroles [<Boolean>]] [formatjson]
|
# [guiroles [<Boolean>]] [formatjson]
|
||||||
# [showitemcountonly]
|
# [showitemcountonly]
|
||||||
def printShowSharedDrives(users, useDomainAdminAccess=False):
|
def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||||
@@ -65893,7 +66079,10 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
|||||||
if td_ouid:
|
if td_ouid:
|
||||||
shareddrive['orgUnit'] = orgUnitIdToPathMap.get(f'id:{td_ouid}', UNKNOWN)
|
shareddrive['orgUnit'] = orgUnitIdToPathMap.get(f'id:{td_ouid}', UNKNOWN)
|
||||||
if showWebViewLink:
|
if showWebViewLink:
|
||||||
|
if showWebViewLink == 'text':
|
||||||
shareddrive['webViewLink'] = 'https://drive.google.com/drive/folders/'+shareddrive['id']
|
shareddrive['webViewLink'] = 'https://drive.google.com/drive/folders/'+shareddrive['id']
|
||||||
|
else:
|
||||||
|
shareddrive['webViewLink'] = '=HYPERLINK("https://drive.google.com/drive/folders/'+shareddrive['id']+'", "'+shareddrive['name']+'")'
|
||||||
if not showFields:
|
if not showFields:
|
||||||
return shareddrive
|
return shareddrive
|
||||||
sshareddrive = {}
|
sshareddrive = {}
|
||||||
@@ -65912,7 +66101,7 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
|||||||
showOrgUnitPaths = True
|
showOrgUnitPaths = True
|
||||||
orgUnitIdToPathMap = None
|
orgUnitIdToPathMap = None
|
||||||
guiRoles = showItemCountOnly = False
|
guiRoles = showItemCountOnly = False
|
||||||
showWebViewLink = False
|
showWebViewLink = ''
|
||||||
while Cmd.ArgumentsRemaining():
|
while Cmd.ArgumentsRemaining():
|
||||||
myarg = getArgument()
|
myarg = getArgument()
|
||||||
if csvPF and myarg == 'todrive':
|
if csvPF and myarg == 'todrive':
|
||||||
@@ -65943,7 +66132,7 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
|||||||
elif myarg == 'guiroles':
|
elif myarg == 'guiroles':
|
||||||
guiRoles = getBoolean()
|
guiRoles = getBoolean()
|
||||||
elif myarg == 'showwebviewlink':
|
elif myarg == 'showwebviewlink':
|
||||||
showWebViewLink = True
|
showWebViewLink = getChoice(SHOWWEBVIEWLINK_CHOICES)
|
||||||
elif myarg == 'showitemcountonly':
|
elif myarg == 'showitemcountonly':
|
||||||
showItemCountOnly = True
|
showItemCountOnly = True
|
||||||
showOrgUnitPaths = False
|
showOrgUnitPaths = False
|
||||||
@@ -77160,6 +77349,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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -302,6 +302,11 @@
|
|||||||
<ContactGroupItem> ::= <ContactGroupID>|<ContactGroupName>
|
<ContactGroupItem> ::= <ContactGroupID>|<ContactGroupName>
|
||||||
<CorporaAttribute> ::= alldrives|allteamdrives|domain|onlyteamdrives|user
|
<CorporaAttribute> ::= alldrives|allteamdrives|domain|onlyteamdrives|user
|
||||||
<CourseAlias> ::= <String>
|
<CourseAlias> ::= <String>
|
||||||
|
<CourseAnnouncementContent> ::=
|
||||||
|
((text <String>)|
|
||||||
|
(textfile <FileName> [charset <Charset>])|
|
||||||
|
(gdoc <UserGoogleDoc>)|
|
||||||
|
(gcsdoc <StorageBucketObjectName>))
|
||||||
<CourseAnnouncementID> ::= <Number>
|
<CourseAnnouncementID> ::= <Number>
|
||||||
<CourseAnnouncementState> ::= draft|published|deleted
|
<CourseAnnouncementState> ::= draft|published|deleted
|
||||||
<CourseID> ::= <Number>|d:<CourseAlias>
|
<CourseID> ::= <Number>|d:<CourseAlias>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
- [Create and update courses](#create-and-update-courses)
|
- [Create and update courses](#create-and-update-courses)
|
||||||
- [Delete courses](#delete-courses)
|
- [Delete courses](#delete-courses)
|
||||||
- [Manage course aliases](#manage-course-aliases)
|
- [Manage course aliases](#manage-course-aliases)
|
||||||
|
- [Manage course announcements](#manage-course-announcements)
|
||||||
- [Manage course topics](#manage-course-topics)
|
- [Manage course topics](#manage-course-topics)
|
||||||
- [Display courses](#display-courses)
|
- [Display courses](#display-courses)
|
||||||
- [Display course counts](#display-course-counts)
|
- [Display course counts](#display-course-counts)
|
||||||
@@ -55,6 +56,11 @@ gam user user@domain.com check|update serviceaccount
|
|||||||
<CourseAliasEntity> ::=
|
<CourseAliasEntity> ::=
|
||||||
<CourseAliasList>|<FileSelector>|<CSVFileSelector>|<CSVkmdSelector>|<CSVDataSelector>
|
<CourseAliasList>|<FileSelector>|<CSVFileSelector>|<CSVkmdSelector>|<CSVDataSelector>
|
||||||
See: https://github.com/GAM-team/GAM/wiki/Collections-of-Items
|
See: https://github.com/GAM-team/GAM/wiki/Collections-of-Items
|
||||||
|
<CourseAnnouncementContent> ::=
|
||||||
|
((text <String>)|
|
||||||
|
(textfile <FileName> [charset <Charset>])|
|
||||||
|
(gdoc <UserGoogleDoc>)|
|
||||||
|
(gcsdoc <StorageBucketObjectName>))
|
||||||
<CourseAnnouncementID> ::= <Number>
|
<CourseAnnouncementID> ::= <Number>
|
||||||
<CourseAnnouncementIDList> ::= "<CourseAnnouncementID>(,<CourseAnnouncementID>)*"
|
<CourseAnnouncementIDList> ::= "<CourseAnnouncementID>(,<CourseAnnouncementID>)*"
|
||||||
<CourseAnnouncementIDEntity> ::=
|
<CourseAnnouncementIDEntity> ::=
|
||||||
@@ -389,17 +395,40 @@ These commands can process multiple courses.
|
|||||||
gam courses <CourseEntity> add alias <CourseAliasEntity>
|
gam courses <CourseEntity> add alias <CourseAliasEntity>
|
||||||
gam courses <CourseEntity> delete alias <CourseAliasEntity>
|
gam courses <CourseEntity> delete alias <CourseAliasEntity>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Manage course announcements
|
||||||
|
These commands can process a single course.
|
||||||
|
```
|
||||||
|
gam course <CourseID> add announcement
|
||||||
|
<CourseAnnouncementContent> [scheduledtime <Time>] [state draft|published]
|
||||||
|
gam course <CourseID> delete announcement <CourseAnnouncementID>
|
||||||
|
gam course <CourseID> update announcement <CourseAnnouncementID>
|
||||||
|
[<CourseAnnouncementContent>] [scheduledtime <Time>] [state published]
|
||||||
|
```
|
||||||
|
These commands can process multiple courses.
|
||||||
|
```
|
||||||
|
gam courses <CourseEntity> add announcement
|
||||||
|
<CourseAnnouncementContent> [scheduledtime <Time>] [state draft|published]
|
||||||
|
gam courses <CourseEntity> delete announcement <CourseAnnouncementIDEntity>
|
||||||
|
gam courses <CourseEntity> update announcement <CourseAnnouncementIDEntity>
|
||||||
|
[<CourseAnnouncementContent>] [scheduledtime <Time>] [state published]
|
||||||
|
```
|
||||||
|
|
||||||
## Manage course topics
|
## Manage course topics
|
||||||
These commands can process a single course.
|
These commands can process a single course.
|
||||||
```
|
```
|
||||||
gam course <CourseID> add topic <CourseTopic>
|
gam course <CourseID> add topic <CourseTopic>
|
||||||
gam course <CourseID> delete topic <CourseTopicID>
|
gam course <CourseID> delete topic <CourseTopicID>
|
||||||
|
gam course <CourseID> update topic <CourseTopicID> <CourseTopic>
|
||||||
|
|
||||||
```
|
```
|
||||||
These commands can process multiple courses.
|
These commands can process multiple courses.
|
||||||
```
|
```
|
||||||
gam courses <CourseEntity> add topic <CourseTopicEntity>
|
gam courses <CourseEntity> add topic <CourseTopicEntity>
|
||||||
gam courses <CourseEntity> delete topic <CourseTopicIDEntity>
|
gam courses <CourseEntity> delete topic <CourseTopicIDEntity>
|
||||||
|
gam courses <CourseEntity> update topic <CourseTopicIDEntity> <CourseTopic>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Display courses
|
## Display courses
|
||||||
```
|
```
|
||||||
gam info course <CourseID> [owneremail] [alias|aliases] [show all|students|teachers] [countsonly]
|
gam info course <CourseID> [owneremail] [alias|aliases] [show all|students|teachers] [countsonly]
|
||||||
|
|||||||
@@ -10,6 +10,20 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
|||||||
|
|
||||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
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 hyperlink` - Displays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||||
|
|
||||||
### 7.10.09
|
### 7.10.09
|
||||||
|
|
||||||
Added option `showwebviewlink` to `gam [<UserTypeEntity>] print|show shareddrives` that
|
Added option `showwebviewlink` to `gam [<UserTypeEntity>] print|show shareddrives` that
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt.
|
|||||||
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||||
admin@server:/Users/admin$ gam version
|
admin@server:/Users/admin$ gam version
|
||||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||||
GAM 7.10.09 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.11.00 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.13.5 64-bit final
|
Python 3.13.5 64-bit final
|
||||||
MacOS Sequoia 15.5 x86_64
|
MacOS Sequoia 15.5 x86_64
|
||||||
@@ -989,7 +989,7 @@ writes the credentials into the file oauth2.txt.
|
|||||||
C:\>del C:\GAMConfig\oauth2.txt
|
C:\>del C:\GAMConfig\oauth2.txt
|
||||||
C:\>gam version
|
C:\>gam version
|
||||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||||
GAM 7.10.09 - https://github.com/GAM-team/GAM - pythonsource
|
GAM 7.11.00 - https://github.com/GAM-team/GAM - pythonsource
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.13.5 64-bit final
|
Python 3.13.5 64-bit final
|
||||||
Windows-10-10.0.17134 AMD64
|
Windows-10-10.0.17134 AMD64
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ gam [<UserTypeEntity>] show shareddrives
|
|||||||
[adminaccess|asadmin] [teamdriveadminquery|query <QueryTeamDrive>]
|
[adminaccess|asadmin] [teamdriveadminquery|query <QueryTeamDrive>]
|
||||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
[fields <SharedDriveFieldNameList>]
|
[fields <SharedDriveFieldNameList>]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
```
|
```
|
||||||
By default, all Shared Drives are displayed; use the following options to select a subset of Shared Drives:
|
By default, all Shared Drives are displayed; use the following options to select a subset of Shared Drives:
|
||||||
@@ -381,6 +381,10 @@ By default, all Shared Drives are displayed; use the following options to select
|
|||||||
* `matchname <REMatchPattern>` - Retrieve Shared Drives with names that match a pattern.
|
* `matchname <REMatchPattern>` - Retrieve Shared Drives with names that match a pattern.
|
||||||
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
|
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
|
||||||
|
|
||||||
|
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||||
|
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||||
|
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||||
|
|
||||||
By default, Gam displays the information as an indented list of keys and values.
|
By default, Gam displays the information as an indented list of keys and values.
|
||||||
* `formatjson` - Display the fields in JSON format.
|
* `formatjson` - Display the fields in JSON format.
|
||||||
```
|
```
|
||||||
@@ -388,7 +392,7 @@ gam [<UserTypeEntity>] print shareddrives [todrive <ToDriveAttribute>*]
|
|||||||
[adminaccess|asadmin] [teamdriveadminquery|query <QueryTeamDrive>]
|
[adminaccess|asadmin] [teamdriveadminquery|query <QueryTeamDrive>]
|
||||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||||
[fields <SharedDriveFieldNameList>]
|
[fields <SharedDriveFieldNameList>]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[formatjson [quotechar <Character>]]
|
[formatjson [quotechar <Character>]]
|
||||||
```
|
```
|
||||||
By default, all Shared Drives are displayed; use the following options to select a subset of Shared Drives:
|
By default, all Shared Drives are displayed; use the following options to select a subset of Shared Drives:
|
||||||
@@ -396,6 +400,10 @@ By default, all Shared Drives are displayed; use the following options to select
|
|||||||
* `matchname <REMatchPattern>` - Retrieve Shared Drives with names that match a pattern.
|
* `matchname <REMatchPattern>` - Retrieve Shared Drives with names that match a pattern.
|
||||||
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
|
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
|
||||||
|
|
||||||
|
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||||
|
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||||
|
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||||
|
|
||||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||||
* `formatjson` - Display the fields in JSON format.
|
* `formatjson` - Display the fields in JSON format.
|
||||||
|
|
||||||
|
|||||||
@@ -1723,9 +1723,9 @@ User,Owner,id,name,ownedByMe,trashed,explicitlyTrashed,directFileCount,directFil
|
|||||||
user@domain.com,user@domain.com,012YenC8f12ALUk9PVA,My Drive,,False,False,100,138212,24,167,189598,79,-1,My Drive
|
user@domain.com,user@domain.com,012YenC8f12ALUk9PVA,My Drive,,False,False,100,138212,24,167,189598,79,-1,My Drive
|
||||||
user@domain.com,user@domain.com,Trash,Trash,,True,True,0,0,1,3,3072,9,-1,Trash
|
user@domain.com,user@domain.com,Trash,Trash,,True,True,0,0,1,3,3072,9,-1,Trash
|
||||||
|
|
||||||
$ gam redirect csv ./MyDriveUsage.csv user user@domain.com print diskusage shareddriveid 0AL5LiIe4dqxZUk9PVA show summaryandtrash
|
$ gam redirect csv ./SharedDriveUsage.csv user user@domain.com print diskusage shareddriveid 0AL5LiIe4dqxZUk9PVA show summaryandtrash
|
||||||
User: user@domain.com, Print 1 Drive Disk Usage
|
User: user@domain.com, Print 1 Drive Disk Usage
|
||||||
$ more MyDriveUsage.csv
|
$ more SharedDriveUsage.csv
|
||||||
User,id,name,trashed,explicitlyTrashed,directFileCount,directFileSize,directFolderCount,totalFileCount,totalFileSize,totalFolderCount,depth,path
|
User,id,name,trashed,explicitlyTrashed,directFileCount,directFileSize,directFolderCount,totalFileCount,totalFileSize,totalFolderCount,depth,path
|
||||||
user@domain.com,0125LiIe4dqxZUk9PVA,TS Shared Drive 1,False,False,16,6144,7,42,73799,25,-1,SharedDrives/TS Shared Drive 1
|
user@domain.com,0125LiIe4dqxZUk9PVA,TS Shared Drive 1,False,False,16,6144,7,42,73799,25,-1,SharedDrives/TS Shared Drive 1
|
||||||
user@domain.com,Trash,Trash,True,True,1,1024,0,1,1024,0,-1,Trash
|
user@domain.com,Trash,Trash,True,True,1,1024,0,1,1024,0,-1,Trash
|
||||||
|
|||||||
@@ -318,26 +318,34 @@ gam <UserTypeEntity> show shareddriveinfo <SharedDriveEntity>
|
|||||||
gam <UserTypeEntity> show shareddrives
|
gam <UserTypeEntity> show shareddrives
|
||||||
[matchname <REMatchPattern>] (role|roles <SharedDriveACLRoleList>)*
|
[matchname <REMatchPattern>] (role|roles <SharedDriveACLRoleList>)*
|
||||||
[fields <SharedDriveFieldNameList>]
|
[fields <SharedDriveFieldNameList>]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[guiroles [<Boolean>] [formatjson]
|
[guiroles [<Boolean>] [formatjson]
|
||||||
```
|
```
|
||||||
By default, Gam displays all Teams Drives accessible by the user.
|
By default, Gam displays all Teams Drives accessible by the user.
|
||||||
* `matchname <REMatchPattern>` - Display Shared Drives with names that match a pattern.
|
* `matchname <REMatchPattern>` - Display Shared Drives with names that match a pattern.
|
||||||
* `(role|roles <SharedDriveACLRoleList>)*` - Display Shared Drives where the user has one of the specified roles.
|
* `(role|roles <SharedDriveACLRoleList>)*` - Display Shared Drives where the user has one of the specified roles.
|
||||||
|
|
||||||
|
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||||
|
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||||
|
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||||
|
|
||||||
By default, Gam displays the information as an indented list of keys and values.
|
By default, Gam displays the information as an indented list of keys and values.
|
||||||
* `formatjson` - Display the fields in JSON format.
|
* `formatjson` - Display the fields in JSON format.
|
||||||
```
|
```
|
||||||
gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
||||||
[matchname <REMatchPattern>] (role|roles <SharedDriveACLRoleList>)*
|
[matchname <REMatchPattern>] (role|roles <SharedDriveACLRoleList>)*
|
||||||
[fields <SharedDriveFieldNameList>]
|
[fields <SharedDriveFieldNameList>]
|
||||||
[showwebviewlink]
|
[showwebviewlink text|hyperlink]
|
||||||
[guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
[guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
||||||
```
|
```
|
||||||
By default, Gam displays all Teams Drives accessible by the user.
|
By default, Gam displays all Teams Drives accessible by the user.
|
||||||
* `matchname <REMatchPattern>` - Display Shared Drives with names that match a pattern.
|
* `matchname <REMatchPattern>` - Display Shared Drives with names that match a pattern.
|
||||||
* `(role|roles <SharedDriveACLRoleList>)*` - Display Shared Drives where the user has one of the specified roles.
|
* `(role|roles <SharedDriveACLRoleList>)*` - Display Shared Drives where the user has one of the specified roles.
|
||||||
|
|
||||||
|
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||||
|
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||||
|
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||||
|
|
||||||
The Google Drive API does not list roles for Shared Drives so GAM generates a role from the capabilities:
|
The Google Drive API does not list roles for Shared Drives so GAM generates a role from the capabilities:
|
||||||
* `commenter - canComment: True, canEdit: False`
|
* `commenter - canComment: True, canEdit: False`
|
||||||
* `reader - canComment: False, canEdit: False`
|
* `reader - canComment: False, canEdit: False`
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
- [Print user counts by OrgUnit](#print-user-counts-by-orgunit)
|
- [Print user counts by OrgUnit](#print-user-counts-by-orgunit)
|
||||||
- [Print user list](#print-user-list)
|
- [Print user list](#print-user-list)
|
||||||
- [Display user counts](#display-user-counts)
|
- [Display user counts](#display-user-counts)
|
||||||
- [Verify domain membership]($verify-domain-membership)
|
- [Verify domain membership](#verify-domain-membership)
|
||||||
|
|
||||||
## API documentation
|
## API documentation
|
||||||
* [Directory API - Users](https://developers.google.com/admin-sdk/directory/reference/rest/v1/users)
|
* [Directory API - Users](https://developers.google.com/admin-sdk/directory/reference/rest/v1/users)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Print the current version of Gam with details
|
Print the current version of Gam with details
|
||||||
```
|
```
|
||||||
gam version
|
gam version
|
||||||
GAM 7.10.09 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.11.00 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.13.5 64-bit final
|
Python 3.13.5 64-bit final
|
||||||
MacOS Sequoia 15.5 x86_64
|
MacOS Sequoia 15.5 x86_64
|
||||||
@@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00
|
|||||||
Print the current version of Gam with details and time offset information
|
Print the current version of Gam with details and time offset information
|
||||||
```
|
```
|
||||||
gam version timeoffset
|
gam version timeoffset
|
||||||
GAM 7.10.09 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.11.00 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.13.5 64-bit final
|
Python 3.13.5 64-bit final
|
||||||
MacOS Sequoia 15.5 x86_64
|
MacOS Sequoia 15.5 x86_64
|
||||||
@@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second
|
|||||||
Print the current version of Gam with extended details and SSL information
|
Print the current version of Gam with extended details and SSL information
|
||||||
```
|
```
|
||||||
gam version extended
|
gam version extended
|
||||||
GAM 7.10.09 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.11.00 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.13.5 64-bit final
|
Python 3.13.5 64-bit final
|
||||||
MacOS Sequoia 15.5 x86_64
|
MacOS Sequoia 15.5 x86_64
|
||||||
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
|
|||||||
Path: /Users/Admin/bin/gam7
|
Path: /Users/Admin/bin/gam7
|
||||||
Version Check:
|
Version Check:
|
||||||
Current: 5.35.08
|
Current: 5.35.08
|
||||||
Latest: 7.10.09
|
Latest: 7.11.00
|
||||||
echo $?
|
echo $?
|
||||||
1
|
1
|
||||||
```
|
```
|
||||||
@@ -72,7 +72,7 @@ echo $?
|
|||||||
Print the current version number without details
|
Print the current version number without details
|
||||||
```
|
```
|
||||||
gam version simple
|
gam version simple
|
||||||
7.10.09
|
7.11.00
|
||||||
```
|
```
|
||||||
In Linux/MacOS you can do:
|
In Linux/MacOS you can do:
|
||||||
```
|
```
|
||||||
@@ -82,7 +82,7 @@ echo $VER
|
|||||||
Print the current version of Gam and address of this Wiki
|
Print the current version of Gam and address of this Wiki
|
||||||
```
|
```
|
||||||
gam help
|
gam help
|
||||||
GAM 7.10.09 - https://github.com/GAM-team/GAM
|
GAM 7.11.00 - https://github.com/GAM-team/GAM
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.13.5 64-bit final
|
Python 3.13.5 64-bit final
|
||||||
MacOS Sequoia 15.5 x86_64
|
MacOS Sequoia 15.5 x86_64
|
||||||
|
|||||||
Reference in New Issue
Block a user