Compare commits
1 Commits
feat/group
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ac29a6d89 |
|
|
@ -35,8 +35,17 @@ tea = Tea() # lazy loader
|
||||||
|
|
||||||
|
|
||||||
@app.command("export-users", help="export users from canvas to csv file")
|
@app.command("export-users", help="export users from canvas to csv file")
|
||||||
def export_users_to_csv(output_file: Path = Argument("students.csv")) -> None:
|
def export_users_to_csv(
|
||||||
tea.pot.canvas.export_users_to_csv(output_file)
|
output_file: Path = Argument("students.csv"),
|
||||||
|
group_prefix: str = Option(
|
||||||
|
"", "--group", help="export members in matched canvas group set"
|
||||||
|
),
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
tea.pot.canvas.export_users_to_csv(output_file, group_prefix=group_prefix)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(e)
|
||||||
|
raise Exit(code=1)
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
@app.command(
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,19 @@ class Settings(BaseSettings):
|
||||||
canvas_course_id: int = 0
|
canvas_course_id: int = 0
|
||||||
|
|
||||||
# gitea
|
# gitea
|
||||||
gitea_domain_name: str = "focs.ji.sjtu.edu.cn"
|
gitea_domain_name: str = "focs.gc.sjtu.edu.cn"
|
||||||
gitea_suffix: str = "/git"
|
gitea_suffix: str = "/git"
|
||||||
gitea_access_token: str = ""
|
gitea_access_token: str = ""
|
||||||
gitea_org_name: str = ""
|
gitea_org_name: str = ""
|
||||||
gitea_debug: bool = False
|
gitea_debug: bool = False
|
||||||
|
|
||||||
# git
|
# git
|
||||||
git_host: str = "ssh://git@focs.ji.sjtu.edu.cn:2222"
|
git_host: str = "ssh://git@focs.gc.sjtu.edu.cn:2222"
|
||||||
repos_dir: str = "./repos"
|
repos_dir: str = "./repos"
|
||||||
default_branch: str = "master"
|
default_branch: str = "master"
|
||||||
|
|
||||||
# mattermost
|
# mattermost
|
||||||
mattermost_domain_name: str = "focs.ji.sjtu.edu.cn"
|
mattermost_domain_name: str = "focs.gc.sjtu.edu.cn"
|
||||||
mattermost_suffix: str = "/mm"
|
mattermost_suffix: str = "/mm"
|
||||||
mattermost_access_token: str = ""
|
mattermost_access_token: str = ""
|
||||||
mattermost_team: str = ""
|
mattermost_team: str = ""
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ def generate_title_and_comment(
|
||||||
f"Generated at {now} from [Gitea Actions #{run_number}]({action_link}), "
|
f"Generated at {now} from [Gitea Actions #{run_number}]({action_link}), "
|
||||||
f"commit {commit_hash}, "
|
f"commit {commit_hash}, "
|
||||||
f"triggered by @{submitter}, "
|
f"triggered by @{submitter}, "
|
||||||
f"run ID [`{run_id}`](https://focs.ji.sjtu.edu.cn/joj-mon/d/{settings.gitea_org_name}?var-Filters=RunID%7C%3D%7C{run_id}).\n"
|
f"run ID [`{run_id}`](https://focs.gc.sjtu.edu.cn/joj-mon/d/{settings.gitea_org_name}?var-Filters=RunID%7C%3D%7C{run_id}).\n"
|
||||||
"Powered by [JOJ3](https://github.com/joint-online-judge/JOJ3) and "
|
"Powered by [JOJ3](https://github.com/joint-online-judge/JOJ3) and "
|
||||||
"[Joint-Teapot](https://github.com/BoYanZh/Joint-Teapot) with ❤️.\n"
|
"[Joint-Teapot](https://github.com/BoYanZh/Joint-Teapot) with ❤️.\n"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ import os
|
||||||
import re
|
import re
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import cast
|
from typing import Dict, List, Tuple, cast
|
||||||
|
|
||||||
from canvasapi import Canvas as PyCanvas
|
from canvasapi import Canvas as PyCanvas
|
||||||
from canvasapi.assignment import Assignment
|
from canvasapi.assignment import Assignment
|
||||||
|
from canvasapi.group import GroupCategory
|
||||||
from canvasapi.user import User
|
from canvasapi.user import User
|
||||||
from patoolib import extract_archive
|
from patoolib import extract_archive
|
||||||
from patoolib.util import PatoolError
|
from patoolib.util import PatoolError
|
||||||
|
|
@ -86,7 +87,73 @@ Teaching Team"""
|
||||||
print(f"Subject: [{settings.gitea_org_name}] Important: wrong Canvas email")
|
print(f"Subject: [{settings.gitea_org_name}] Important: wrong Canvas email")
|
||||||
print(f"Body:\n{SAMPLE_EMAIL_BODY}")
|
print(f"Body:\n{SAMPLE_EMAIL_BODY}")
|
||||||
|
|
||||||
def export_users_to_csv(self, filename: Path) -> None:
|
def _get_group_set_by_prefix(self, group_prefix: str) -> GroupCategory:
|
||||||
|
group_sets = list(self.course.get_group_categories())
|
||||||
|
exact_match = first(group_sets, lambda group_set: group_set.name == group_prefix)
|
||||||
|
if exact_match is not None:
|
||||||
|
return cast(GroupCategory, exact_match)
|
||||||
|
matched_group_sets = [
|
||||||
|
group_set
|
||||||
|
for group_set in group_sets
|
||||||
|
if group_set.name.startswith(group_prefix)
|
||||||
|
]
|
||||||
|
if len(matched_group_sets) == 0:
|
||||||
|
raise ValueError(
|
||||||
|
f'Canvas group set with prefix "{group_prefix}" not found'
|
||||||
|
)
|
||||||
|
if len(matched_group_sets) > 1:
|
||||||
|
matched_names = ", ".join(group_set.name for group_set in matched_group_sets)
|
||||||
|
raise ValueError(
|
||||||
|
f'Multiple Canvas group sets match prefix "{group_prefix}": {matched_names}'
|
||||||
|
)
|
||||||
|
return matched_group_sets[0]
|
||||||
|
|
||||||
|
def _parse_group_name(self, group_set_name: str, group_name: str) -> Tuple[int, str]:
|
||||||
|
match = re.fullmatch(rf"{re.escape(group_set_name)}\s*(\d+)", group_name)
|
||||||
|
if match is None:
|
||||||
|
raise ValueError(
|
||||||
|
f'Canvas group "{group_name}" in group set "{group_set_name}" '
|
||||||
|
+ f'should be named as "{group_set_name} <number>"'
|
||||||
|
)
|
||||||
|
group_number = int(match.group(1))
|
||||||
|
return group_number, f"{group_set_name}{group_number:02}"
|
||||||
|
|
||||||
|
def _get_group_users_csv_rows(self, group_prefix: str) -> Tuple[str, List[List[str]]]:
|
||||||
|
group_set = self._get_group_set_by_prefix(group_prefix)
|
||||||
|
users_by_id: Dict[int, User] = {user.id: user for user in self.users}
|
||||||
|
rows = []
|
||||||
|
groups = []
|
||||||
|
for group in group_set.get_groups():
|
||||||
|
group_number, normalized_group_name = self._parse_group_name(
|
||||||
|
group_set.name, group.name
|
||||||
|
)
|
||||||
|
groups.append((group_number, normalized_group_name, group))
|
||||||
|
for _, normalized_group_name, group in sorted(groups, key=lambda item: item[0]):
|
||||||
|
group_rows = []
|
||||||
|
for membership in group.get_memberships():
|
||||||
|
user = users_by_id.get(membership.user_id)
|
||||||
|
if user is None:
|
||||||
|
logger.warning(
|
||||||
|
f"user with id {membership.user_id} found in {group.name} "
|
||||||
|
+ "but not found in course users"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
group_rows.append(
|
||||||
|
[user.name, user.sis_id, user.login_id, normalized_group_name]
|
||||||
|
)
|
||||||
|
rows.extend(sorted(group_rows))
|
||||||
|
return group_set.name, rows
|
||||||
|
|
||||||
|
def export_users_to_csv(self, filename: Path, group_prefix: str = "") -> None:
|
||||||
|
if group_prefix:
|
||||||
|
group_set_name, rows = self._get_group_users_csv_rows(group_prefix)
|
||||||
|
with open(filename, mode="w", newline="") as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
for row in rows:
|
||||||
|
writer.writerow(row)
|
||||||
|
logger.info(f'Users in group set "{group_set_name}" exported to {filename}')
|
||||||
|
return
|
||||||
|
|
||||||
with open(filename, mode="w", newline="") as file:
|
with open(filename, mode="w", newline="") as file:
|
||||||
writer = csv.writer(file)
|
writer = csv.writer(file)
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user