Skip to content

Commit 1cf7e67

Browse files
committedOct 15, 2022
Struct teams
Signed-off-by: Toomore Chiang <toomore0929@gmail.com>
1 parent fd3b25b commit 1cf7e67

21 files changed

+656
-359
lines changed
 

‎Dockerfile-app-dev

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ ADD ./view/utils.py ./view/utils.py
103103

104104
ADD ./structs/__init__.py ./structs/__init__.py
105105
ADD ./structs/projects.py ./structs/projects.py
106+
ADD ./structs/teams.py ./structs/teams.py
106107
ADD ./structs/users.py ./structs/users.py
107108

108109
ADD ./api/__init__.py ./api/__init__.py

‎api/apistructs/teams.py

+14
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,17 @@ class TeamItemUpdateOutput(TeamItemUpdateInput):
3131
class TeamAddressBookOutput(BaseModel):
3232
''' Team address book output '''
3333
datas: list[UserItem] = Field(description='list of users info')
34+
35+
36+
class TeamCreateInput(BaseModel):
37+
''' Team create input '''
38+
id: str = Field(description='team id')
39+
name: str = Field(description='team name')
40+
41+
class Config: # pylint: disable=too-few-public-methods
42+
''' Config '''
43+
anystr_strip_whitespace = True
44+
45+
46+
class TeamCreateOutput(TeamCreateInput):
47+
''' TeamCreateOutput '''

‎api/routers/members.py

+40-22
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,43 @@ async def members_past(
3232
'''
3333
result = MembersOut()
3434
for team in Team.list_by_pid(pid=pid):
35-
data = {}
36-
data['name'] = team['name']
37-
data['tid'] = team['tid']
35+
if not team.chiefs:
36+
continue
3837

39-
data['chiefs'] = []
40-
chiefs_infos = User.get_info(uids=team['chiefs'])
41-
for uid in team['chiefs']:
38+
data_chiefs: list[MembersInfo] = []
39+
chiefs_infos = User.get_info(uids=team.chiefs)
40+
for uid in team.chiefs:
4241
if uid not in chiefs_infos:
4342
continue
4443

4544
_user = chiefs_infos[uid]
4645
h_msg = hashlib.md5()
4746
h_msg.update(_user['oauth']['email'].encode('utf-8'))
48-
data['chiefs'].append(MembersInfo.parse_obj({
47+
data_chiefs.append(MembersInfo.parse_obj({
4948
'name': _user['profile']['badge_name'],
5049
'email_hash': h_msg.hexdigest(),
5150
}))
5251

53-
data['members'] = []
54-
for _user in User.get_info(uids=list(set(team['members']) - set(team['chiefs']))).values():
52+
data_members: list[MembersInfo] = []
53+
54+
uids = set()
55+
if team.members:
56+
uids.update(team.members)
57+
if team.chiefs:
58+
uids.update(team.chiefs)
59+
60+
for _user in User.get_info(uids=list(uids)).values():
5561
h_msg = hashlib.md5()
5662
h_msg.update(_user['oauth']['email'].encode('utf-8'))
57-
data['members'].append(MembersInfo.parse_obj({
63+
data_members.append(MembersInfo.parse_obj({
5864
'name': _user['profile']['badge_name'],
5965
'email_hash': h_msg.hexdigest(),
6066
}))
6167

62-
result.data.append(MembersTeams.parse_obj(data))
68+
result.data.append(MembersTeams.parse_obj(
69+
{'name': team.name, 'tid': team.id,
70+
'chiefs': data_chiefs, 'members': data_members}
71+
))
6372

6473
if result.data:
6574
return result
@@ -82,34 +91,43 @@ async def members(
8291
'''
8392
result = MembersOut()
8493
for team in Team.list_by_pid(pid=pid):
85-
data = {}
86-
data['name'] = team['name']
87-
data['tid'] = team['tid']
94+
if not team.chiefs:
95+
continue
8896

89-
data['chiefs'] = []
90-
chiefs_infos = User.get_info(uids=team['chiefs'])
91-
for uid in team['chiefs']:
97+
data_chiefs: list[MembersInfo] = []
98+
chiefs_infos = User.get_info(uids=team.chiefs)
99+
for uid in team.chiefs:
92100
if uid not in chiefs_infos:
93101
continue
94102

95103
user = chiefs_infos[uid]
96104
h_msg = hashlib.md5()
97105
h_msg.update(user['oauth']['email'].encode('utf-8'))
98-
data['chiefs'].append(MembersInfo.parse_obj({
106+
data_chiefs.append(MembersInfo.parse_obj({
99107
'name': user['profile']['badge_name'],
100108
'email_hash': h_msg.hexdigest(),
101109
}))
102110

103-
data['members'] = []
104-
for user in User.get_info(uids=list(set(team['members']) - set(team['chiefs']))).values():
111+
data_members: list[MembersInfo] = []
112+
113+
uids = set()
114+
if team.members:
115+
uids.update(team.members)
116+
if team.chiefs:
117+
uids.update(team.chiefs)
118+
119+
for user in User.get_info(uids=list(uids)).values():
105120
h_msg = hashlib.md5()
106121
h_msg.update(user['oauth']['email'].encode('utf-8'))
107-
data['members'].append(MembersInfo.parse_obj({
122+
data_members.append(MembersInfo.parse_obj({
108123
'name': user['profile']['badge_name'],
109124
'email_hash': h_msg.hexdigest(),
110125
}))
111126

112-
result.data.append(MembersTeams.parse_obj(data))
127+
result.data.append(MembersTeams.parse_obj(
128+
{'name': team.name, 'tid': team.id,
129+
'chiefs': data_chiefs, 'members': data_members}
130+
))
113131

114132
if result.data:
115133
return result

‎api/routers/projects.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
ProjectItemUpdateOutput,
1111
ProjectTeamDietaryHabitOutput,
1212
ProjectTeamsOutput)
13+
from api.apistructs.teams import TeamCreateInput, TeamCreateOutput
1314
from api.dependencies import get_current_user
1415
from module.dietary_habit import DietaryHabitItemsName, DietaryHabitItemsValue
1516
from module.project import Project
@@ -126,6 +127,34 @@ async def projects_teams(
126127
return ProjectTeamsOutput.parse_obj({'teams': teams})
127128

128129

130+
@router.post('/{pid}/teams',
131+
summary='Create a new team in project.',
132+
response_model=TeamCreateOutput,
133+
responses={
134+
status.HTTP_404_NOT_FOUND: {'description': 'Project not found'}},
135+
response_model_by_alias=False,
136+
response_model_exclude_none=True,
137+
)
138+
async def projects_teams_create(
139+
create_date: TeamCreateInput,
140+
pid: str = Path(..., description='project id'),
141+
current_user: dict[str, Any] = Depends( # pylint: disable=unused-argument
142+
get_current_user),
143+
) -> TeamCreateOutput | None:
144+
''' Create a new team in project '''
145+
project = Project.get(pid=pid)
146+
if not project:
147+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
148+
149+
if current_user['uid'] not in project.owners:
150+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
151+
152+
result = Team.create(
153+
pid=pid, tid=create_date.id, name=create_date.name, owners=project.owners)
154+
155+
return TeamCreateOutput.parse_obj(result)
156+
157+
129158
@router.get('/{pid}/teams/dietary_habit',
130159
summary='Lists of dietary habit statistics in project.',
131160
response_model=list[ProjectTeamDietaryHabitOutput],
@@ -141,8 +170,13 @@ async def projects_teams_dietary_habit(
141170
''' Lists of dietary habit statistics in project '''
142171
all_users = {}
143172
for team in Team.list_by_pid(pid=pid):
144-
for uid in team['chiefs']+team['members']:
145-
all_users[uid] = {'tid': team['tid']}
173+
if team.chiefs:
174+
for uid in team.chiefs:
175+
all_users[uid] = {'tid': team.id}
176+
177+
if team.members:
178+
for uid in team.members:
179+
all_users[uid] = {'tid': team.id}
146180

147181
user_infos = User.get_info(
148182
uids=list(all_users.keys()), need_sensitive=True)

‎api/routers/sender.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from api.dependencies import get_current_user
1212
from module.sender import SenderCampaign, SenderReceiver
1313
from module.team import Team
14+
from structs.teams import TeamUsers
1415

1516
router = APIRouter(
1617
prefix='/sender',
@@ -36,7 +37,8 @@ async def sender_all(
3637
if not team:
3738
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
3839

39-
if current_user['uid'] not in (team['owners'] + team['chiefs'] + team['members']):
40+
teamusers = TeamUsers.parse_obj(team)
41+
if current_user['uid'] not in (teamusers.owners + teamusers.chiefs + teamusers.members):
4042
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
4143

4244
campaigns = []
@@ -96,7 +98,8 @@ async def sender_upload_receiver_lists_replace(
9698
if not team:
9799
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
98100

99-
if current_user['uid'] not in (team['owners'] + team['chiefs'] + team['members']):
101+
teamusers = TeamUsers.parse_obj(team)
102+
if current_user['uid'] not in (teamusers.owners + teamusers.chiefs + teamusers.members):
100103
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
101104

102105
csv_rows = await sender_upload_receiver(pid=pid, tid=tid, cid=cid,
@@ -125,7 +128,8 @@ async def sender_upload_receiver_lists_update(
125128
if not team:
126129
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
127130

128-
if current_user['uid'] not in (team['owners'] + team['chiefs'] + team['members']):
131+
teamusers = TeamUsers.parse_obj(team)
132+
if current_user['uid'] not in (teamusers.owners + teamusers.chiefs + teamusers.members):
129133
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
130134

131135
csv_rows = await sender_upload_receiver(pid=pid, tid=tid, cid=cid,
@@ -152,7 +156,8 @@ async def sender_upload_receiver_lists_delete(
152156
if not team:
153157
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
154158

155-
if current_user['uid'] not in (team['owners'] + team['chiefs'] + team['members']):
159+
teamusers = TeamUsers.parse_obj(team)
160+
if current_user['uid'] not in (teamusers.owners + teamusers.chiefs + teamusers.members):
156161
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
157162

158163
return UploadReceiverOutput.parse_obj({

‎api/routers/teams.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from module.mattermost_bot import MattermostTools
1111
from module.team import Team
1212
from module.users import User
13+
from structs.teams import TeamUsers
1314

1415
router = APIRouter(
1516
prefix='/teams',
@@ -71,14 +72,15 @@ async def teams_one_update(
7172
if not team:
7273
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
7374

75+
teamusers = TeamUsers.parse_obj(team)
7476
include: set[str] | None
75-
if current_user['uid'] in team['owners']:
77+
if current_user['uid'] in teamusers.owners:
7678
include = None
7779

78-
elif current_user['uid'] in team['chiefs']:
80+
elif current_user['uid'] in teamusers.chiefs:
7981
include = {'name', 'desc'}
8082

81-
if current_user['uid'] in team['owners'] or include is not None:
83+
if current_user['uid'] in teamusers.owners or include is not None:
8284
updated = Team.update_setting(
8385
pid=pid, tid=tid, data=update_data.dict(include=include))
8486

@@ -108,12 +110,13 @@ async def teams_one_address_book(
108110
if not team:
109111
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
110112

111-
if current_user['uid'] not in (team['owners'] + team['chiefs'] + team['members']):
113+
teamusers = TeamUsers.parse_obj(team)
114+
if current_user['uid'] not in (teamusers.owners + teamusers.chiefs + teamusers.members):
112115
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
113116

114117
uids = set()
115-
uids.update(team['chiefs'])
116-
uids.update(team['members'])
118+
uids.update(teamusers.chiefs)
119+
uids.update(teamusers.members)
117120
users_info = User.get_info(uids=list(uids))
118121

119122
datas = []
@@ -128,7 +131,7 @@ async def teams_one_address_book(
128131
{'id': uid,
129132
'badge_name': users_info[uid]['profile']['badge_name'],
130133
'avatar': users_info[uid]['oauth']['picture'],
131-
'is_chief': uid in team['chiefs'],
134+
'is_chief': uid in teamusers.chiefs,
132135
'chat': chat,
133136
}))
134137

‎celery_task/task_mail_sys.py

+57-38
Original file line numberDiff line numberDiff line change
@@ -66,43 +66,49 @@ def mail_member_waiting(sender):
6666
sort=(('create_at', 1), )):
6767
team = Team.get(raw['pid'], raw['tid'])
6868

69-
uids = []
70-
uids.extend(team['chiefs'])
69+
if not team:
70+
continue
71+
72+
uids: list[str] = []
73+
if team.chiefs:
74+
uids.extend(team.chiefs)
75+
7176
uids.append(raw['uid'])
7277

7378
users = User.get_info(uids=uids)
7479

7580
mmt = MattermostTools(token=setting.MATTERMOST_BOT_TOKEN,
7681
base_url=setting.MATTERMOST_BASEURL)
7782

78-
for uid in team['chiefs']:
79-
body = template.render(
80-
name=users[uid]['profile']['badge_name'],
81-
uid=raw['uid'],
82-
apply_name=users[raw['uid']]['profile']['badge_name'],
83-
team_name=team['name'], pid=team['pid'], tid=team['tid'], )
84-
85-
raw_mail = awsses.raw_mail(
86-
to_addresses=(dict(
87-
name=users[uid]['profile']['badge_name'], mail=users[uid]['oauth']['email']), ),
88-
subject=f"申請加入通知信 - {users[raw['uid']]['profile']['badge_name']}",
89-
body=body,
90-
)
91-
92-
resp = mail_member_send.apply_async(
93-
kwargs={'raw_mail': raw_mail.as_string(), 'rid': str(raw['_id'])})
94-
logger.info(resp)
95-
96-
mid = mmt.find_possible_mid(uid=uid)
97-
if mid:
98-
channel_info = mmt.create_a_direct_message(
99-
users=(mid, setting.MATTERMOST_BOT_ID)).json()
100-
101-
resp = mmt.posts(
102-
channel_id=channel_info['id'],
103-
message=f"收到 **{users[raw['uid']]['profile']['badge_name']}** 申請加入 **{team['name']}**,前往 [管理組員](https://volunteer.coscup.org/team/{team['pid']}/{team['tid']}/edit_user)", # pylint: disable=line-too-long
83+
if team.chiefs:
84+
for uid in team.chiefs:
85+
body = template.render(
86+
name=users[uid]['profile']['badge_name'],
87+
uid=raw['uid'],
88+
apply_name=users[raw['uid']]['profile']['badge_name'],
89+
team_name=team.name, pid=team.pid, tid=team.id, )
90+
91+
raw_mail = awsses.raw_mail(
92+
to_addresses=(dict(
93+
name=users[uid]['profile']['badge_name'], mail=users[uid]['oauth']['email']), ),
94+
subject=f"申請加入通知信 - {users[raw['uid']]['profile']['badge_name']}",
95+
body=body,
10496
)
105-
logger.info(resp.json())
97+
98+
resp = mail_member_send.apply_async(
99+
kwargs={'raw_mail': raw_mail.as_string(), 'rid': str(raw['_id'])})
100+
logger.info(resp)
101+
102+
mid = mmt.find_possible_mid(uid=uid)
103+
if mid:
104+
channel_info = mmt.create_a_direct_message(
105+
users=(mid, setting.MATTERMOST_BOT_ID)).json()
106+
107+
resp = mmt.posts(
108+
channel_id=channel_info['id'],
109+
message=f"收到 **{users[raw['uid']]['profile']['badge_name']}** 申請加入 **{team.name}**,前往 [管理組員](https://volunteer.coscup.org/team/{team.pid}/{team.id}/edit_user)", # pylint: disable=line-too-long
110+
)
111+
logger.info(resp.json())
106112

107113

108114
@app.task(bind=True, name='mail.member.deny',
@@ -123,19 +129,26 @@ def mail_member_deny(sender):
123129
{'done.mail': {'$exists': False}, 'case': 'deny'},
124130
sort=(('create_at', 1), )):
125131
team = Team.get(raw['pid'], raw['tid'])
126-
project = Project().get(pid=team['pid'])
132+
133+
if not team:
134+
continue
135+
136+
project = Project().get(pid=team.pid)
137+
138+
if not project:
139+
continue
127140

128141
user = User.get_info(uids=(raw['uid'], ))[raw['uid']]
129142
body = template.render(
130143
name=user['profile']['badge_name'],
131-
team_name=team['name'],
132-
project_name=project['name'],
133-
pid=team['pid'], )
144+
team_name=team.name,
145+
project_name=project.name,
146+
pid=team.pid, )
134147

135148
raw_mail = awsses.raw_mail(
136149
to_addresses=(
137150
dict(name=user['profile']['badge_name'], mail=user['oauth']['email']), ),
138-
subject=f"申請加入 {team['name']} 未核准",
151+
subject=f"申請加入 {team.name} 未核准",
139152
body=body,
140153
)
141154

@@ -163,16 +176,19 @@ def mail_member_add(sender):
163176
sort=(('create_at', 1), )):
164177
team = Team.get(raw['pid'], raw['tid'])
165178

179+
if not team:
180+
continue
181+
166182
user = User.get_info(uids=(raw['uid'], ))[raw['uid']]
167183

168184
body = template.render(
169185
name=user['profile']['badge_name'],
170-
team_name=team['name'], pid=team['pid'], tid=team['tid'], )
186+
team_name=team.name, pid=team.pid, tid=team.id, )
171187

172188
raw_mail = awsses.raw_mail(
173189
to_addresses=(
174190
dict(name=user['profile']['badge_name'], mail=user['oauth']['email']), ),
175-
subject=f"申請加入 {team['name']} 核准",
191+
subject=f"申請加入 {team.name} 核准",
176192
body=body,
177193
)
178194

@@ -202,16 +218,19 @@ def mail_member_del(sender):
202218
sort=(('create_at', 1), )):
203219
team = Team.get(raw['pid'], raw['tid'])
204220

221+
if not team:
222+
continue
223+
205224
user = User.get_info(uids=(raw['uid'], ))[raw['uid']]
206225

207226
body = template.render(
208227
name=user['profile']['badge_name'],
209-
team_name=team['name'], )
228+
team_name=team.name, )
210229

211230
raw_mail = awsses.raw_mail(
212231
to_addresses=(
213232
dict(name=user['profile']['badge_name'], mail=user['oauth']['email']), ),
214-
subject=f"您已被移除 {team['name']} 的組員資格!",
233+
subject=f"您已被移除 {team.name} 的組員資格!",
215234
body=body,
216235
)
217236

‎celery_task/task_service_sync.py

+31-20
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,22 @@ def service_sync_gsuite_team_members(sender, **kwargs):
122122
if 'to_team' in kwargs:
123123
to_team = Team.get(pid=kwargs['to_team'][0], tid=kwargs['to_team'][1])
124124

125-
if 'mailling' not in to_team or not to_team['mailling']:
125+
if not to_team.mailling:
126126
return
127127

128-
mailling = to_team['mailling']
128+
mailling = to_team.mailling
129129

130130
else:
131-
if 'mailling' not in team or not team['mailling']:
131+
if not team.mailling:
132132
return
133133

134-
mailling = team['mailling']
134+
mailling = team.mailling
135135

136136
uids = []
137-
uids.extend(team['chiefs'])
138-
uids.extend(team['members'])
137+
if team.chiefs:
138+
uids.extend(team['chiefs'])
139+
if team.members:
140+
uids.extend(team['members'])
139141

140142
users_info = User.get_info(uids=uids)
141143

@@ -231,8 +233,10 @@ def service_sync_mattermost_projectuserin_channel(sender):
231233
for pid, value in pids.items():
232234
uids = set()
233235
for team in Team.list_by_pid(pid=pid):
234-
uids.update(team['chiefs'])
235-
uids.update(team['members'])
236+
if team.chiefs:
237+
uids.update(team.chiefs)
238+
if team.members:
239+
uids.update(team.members)
236240

237241
for uid in uids:
238242
mid = mmt.find_possible_mid(uid=uid)
@@ -258,23 +262,30 @@ def service_sync_mattermost_users_position(sender, **kwargs):
258262
for pid in pids:
259263
users = {}
260264
for team in Team.list_by_pid(pid=pid):
261-
team_name = team['name'].split('-')[0].strip()
265+
team_name = team.name.split('-')[0].strip()
266+
267+
if team.chiefs:
268+
for chief in team.chiefs:
269+
if chief not in users:
270+
users[chief] = []
271+
272+
if team.tid == 'coordinator':
273+
users[chief].append('🌟總召')
274+
else:
275+
users[chief].append(f'⭐️組長@{team_name}')
262276

263-
for chief in team['chiefs']:
264-
if chief not in users:
265-
users[chief] = []
277+
if team.members:
278+
if team.chiefs:
279+
members = list(set(team.members) - set(team.chiefs))
266280

267-
if team['tid'] == 'coordinator':
268-
users[chief].append('🌟總召')
269281
else:
270-
users[chief].append(f'⭐️組長@{team_name}')
282+
members = team.members
271283

272-
team['members'] = set(team['members']) - set(team['chiefs'])
273-
for member in team['members']:
274-
if member not in users:
275-
users[member] = []
284+
for member in members:
285+
if member not in users:
286+
users[member] = []
276287

277-
users[member].append(f'{team_name}(組員)')
288+
users[member].append(f'{team_name}(組員)')
278289

279290
mmt = MattermostTools(token=setting.MATTERMOST_BOT_TOKEN,
280291
base_url=setting.MATTERMOST_BASEURL)

‎docs_dev/docs/dev/api.md

+11-5
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,24 @@ in the future will be published here.
2222
- [ ] Remove all sessions.
2323

2424
=== "User"
25-
- [ ] User self info
26-
- [ ] GET
27-
- [ ] POST
25+
- [x] User self info
26+
- [x] GET
27+
- [x] POST: bank, resume, address, dietary habit, real profile
2828

2929
=== "Users"
3030
- [ ] Get users public info
3131

3232
=== "Projects"
33-
- [ ] List all projects.
33+
- [x] List all projects.
34+
- [x] Creat a project.
35+
- [ ] Update traffic subsidy.
3436

3537
=== "Teams"
36-
- [ ] List all Teams.
38+
- [x] List all Teams.
39+
- [ ] Create a new team.
40+
- [ ] Add / delete chiefs
41+
- [ ] Add / delete members.
42+
- [ ] List available forms.
3743

3844
=== "Tasks"
3945
- [ ] List all tasks.

‎models/teamdb.py

+11-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pymongo.collection import ReturnDocument
66

77
from models.base import DBBase
8+
from structs.teams import TeamBase
89

910

1011
class TeamDB(DBBase):
@@ -76,22 +77,22 @@ def default(self) -> dict[str, Any]:
7677
self.make_create_at(result)
7778
return result
7879

79-
def add(self, data: dict[str, Any]) -> dict[str, Any]:
80+
def add(self, data: TeamBase) -> TeamBase:
8081
''' Add data
8182
8283
Args:
83-
data (dict): The data to inserted / updated.
84+
data (TeamBase): The data to inserted / updated.
8485
8586
Returns:
8687
Return the inserted / updated data.
8788
8889
'''
89-
return self.find_one_and_update(
90+
return TeamBase.parse_obj(self.find_one_and_update(
9091
{'pid': self.pid, 'tid': self.tid},
91-
{'$set': data},
92+
{'$set': data.dict(exclude_none=True)},
9293
upsert=True,
9394
return_document=ReturnDocument.AFTER,
94-
)
95+
))
9596

9697
def update_setting(self, data: dict[str, Any]) -> dict[str, Any]:
9798
''' update setting
@@ -130,14 +131,17 @@ def update_users(self, field: str,
130131
{'pid': self.pid, 'tid': self.tid},
131132
{'$pullAll': {field: del_uids}})
132133

133-
def get(self) -> Optional[dict[str, Any]]:
134+
def get(self) -> TeamBase | None:
134135
''' Get data
135136
136137
Returns:
137138
Return the team info in `pid`, `tid`.
138139
139140
'''
140-
return self.find_one({'pid': self.pid, 'tid': self.tid})
141+
for team in self.find({'pid': self.pid, 'tid': self.tid}):
142+
return TeamBase.parse_obj(team)
143+
144+
return None
141145

142146
def add_tag_member(self, tag_data: dict[str, str]) -> None:
143147
''' Add tag member

‎module/team.py

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
''' Team '''
2-
from typing import Any, Optional
2+
from typing import Any, Generator, Optional
33
from uuid import uuid4
44

55
from pymongo.cursor import Cursor
66

77
from models.teamdb import TeamDB, TeamMemberChangedDB, TeamMemberTagsDB
8+
from structs.teams import TeamBase, TeamUsers
89

910

1011
class Team:
1112
''' Team module '''
1213

1314
@staticmethod
14-
def create(pid: str, tid: str, name: str, owners: list[str]) -> dict[str, Any]:
15+
def create(pid: str, tid: str, name: str, owners: list[str]) -> TeamBase:
1516
''' Create team
1617
1718
:param str pid: project id
@@ -24,11 +25,8 @@ def create(pid: str, tid: str, name: str, owners: list[str]) -> dict[str, Any]:
2425
raise Exception('lost required')
2526

2627
teamdb = TeamDB(pid, tid)
27-
28-
data = teamdb.default()
29-
data['name'] = name
30-
data['owners'].extend(owners)
31-
28+
data = TeamBase.parse_obj(
29+
{'pid': pid, 'tid': tid, 'name': name, 'owners': owners})
3230
return teamdb.add(data)
3331

3432
@staticmethod
@@ -68,21 +66,24 @@ def update_members(pid: str, tid: str,
6866
pid=pid, tid=tid, action={'add': add_uids, 'del': del_uids})
6967

7068
@staticmethod
71-
def list_by_pid(pid: str, show_all: bool = False) -> Cursor[dict[str, Any]]:
69+
def list_by_pid(pid: str, show_all: bool = False) -> Generator[TeamBase, None, None]:
7270
''' List all team in project
7371
7472
:param str pid: project id
7573
7674
'''
7775
if show_all:
78-
return TeamDB('', '').find({'pid': pid})
76+
for team in TeamDB('', '').find({'pid': pid}):
77+
yield TeamBase.parse_obj(team)
7978

80-
return TeamDB('', '').find({
81-
'pid': pid,
82-
'$or': [{'disabled': {'$exists': False}}, {'disabled': False}]})
79+
else:
80+
for team in TeamDB('', '').find({
81+
'pid': pid,
82+
'$or': [{'disabled': {'$exists': False}}, {'disabled': False}]}):
83+
yield TeamBase.parse_obj(team)
8384

8485
@staticmethod
85-
def get(pid: str, tid: str) -> Optional[dict[str, Any]]:
86+
def get(pid: str, tid: str) -> TeamBase | None:
8687
''' Get team data
8788
8889
:param str pid: project id
@@ -161,7 +162,8 @@ def get_users(pid: str, tids: list[str]) -> dict[str, Any]:
161162
if not team:
162163
raise Exception(f"no team: {tid}")
163164

164-
users[tid] = team['chiefs'] + team['members']
165+
teamusers = TeamUsers.parse_obj(team)
166+
users[tid] = teamusers.chiefs + teamusers.members
165167

166168
return users
167169

‎poetry.lock

+20-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ fastapi = "^0.85.0"
2727
uvicorn = {extras = ["standard"], version = "^0.18.3"}
2828
python-multipart = "^0.0.5"
2929
passlib = {extras = ["bcrypt"], version = "^1.7.4"}
30+
types-markdown = "^3.4.2.1"
3031

3132
[tool.poetry.dev-dependencies]
3233
mongomock = "^4.0.0"

‎structs/teams.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
''' Teams Structs '''
2+
from datetime import datetime
3+
4+
from pydantic import BaseModel, EmailStr, Field
5+
6+
7+
class TagMembers(BaseModel):
8+
''' TagMembers '''
9+
id: str = Field(description='random id')
10+
name: str = Field(description='tag name')
11+
12+
13+
class TeamBase(BaseModel):
14+
''' TeamBase '''
15+
id: str = Field(description='`tid`, team id', alias='tid')
16+
pid: str = Field(description='`pid`, project id')
17+
name: str = Field(description='team name')
18+
owners: list[str] | None = Field(description="list of owners' uids")
19+
chiefs: list[str] | None = Field(description="list of chiefs' uids")
20+
members: list[str] | None = Field(description="list of members' uids")
21+
desc: str | None = Field(description='desc')
22+
mailling: EmailStr | None = Field(description='mailing list for team')
23+
headcount: int | None = Field(description='the headcount of team')
24+
public_desc: str | None = Field(description='public desc for not members')
25+
disabled: bool = Field(default=False, description='disabled the team')
26+
tag_members: list[TagMembers] | None = Field(
27+
description='tags for members')
28+
created_at: datetime | None = Field(description='create at')
29+
30+
class Config: # pylint: disable=too-few-public-methods
31+
''' Config '''
32+
anystr_strip_whitespace = True
33+
34+
35+
class TeamUsers(BaseModel):
36+
''' Default list type for owners, chiefs, members '''
37+
owners: list[str] = Field(default_factory=list,
38+
description="list of owners' uids")
39+
chiefs: list[str] = Field(default_factory=list,
40+
description="list of chiefs' uids")
41+
members: list[str] = Field(
42+
default_factory=list, description="list of members' uids")

‎view/api.py

+32-16
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,70 @@
11
''' API '''
22
import hashlib
33

4-
from flask import Blueprint, jsonify, request
4+
from flask import Blueprint, Response, jsonify, request
5+
from pydantic import BaseModel, Field
56

67
from module.team import Team
78
from module.users import User
9+
from structs.teams import TeamUsers
810

911
VIEW_API = Blueprint('api', __name__, url_prefix='/api')
1012

1113

1214
@VIEW_API.route('/')
13-
def index():
15+
def index() -> str:
1416
''' Index page '''
1517
return 'hi'
1618

1719

20+
class Member(BaseModel):
21+
''' Member '''
22+
name: str
23+
email_hash: str
24+
25+
26+
class ProjectMembersOutput(BaseModel):
27+
''' ProjectMembersOutput '''
28+
name: str = Field(default='')
29+
tid: str = Field(default='')
30+
chiefs: list[Member] = Field(default_factory=list)
31+
members: list[Member] = Field(default_factory=list)
32+
33+
1834
@VIEW_API.route('/members')
19-
def project_members():
35+
def project_members() -> Response:
2036
''' List all members '''
2137
pid = request.args['pid']
2238

2339
result = []
2440
for team in Team.list_by_pid(pid=pid):
25-
data = {}
26-
data['name'] = team['name']
27-
data['tid'] = team['tid']
41+
data = ProjectMembersOutput()
42+
data.name = team.name
43+
data.tid = team.id
2844

29-
data['chiefs'] = []
30-
chiefs_infos = User.get_info(uids=team['chiefs'])
31-
for uid in team['chiefs']:
45+
teamusers = TeamUsers.parse_obj(team)
46+
chiefs_infos = User.get_info(uids=teamusers.chiefs)
47+
for uid in teamusers.chiefs:
3248
if uid not in chiefs_infos:
3349
continue
3450

3551
user = chiefs_infos[uid]
3652
h_msg = hashlib.md5()
3753
h_msg.update(user['oauth']['email'].encode('utf-8'))
38-
data['chiefs'].append({
54+
data.chiefs.append(Member.parse_obj({
3955
'name': user['profile']['badge_name'],
4056
'email_hash': h_msg.hexdigest(),
41-
})
57+
}))
4258

43-
data['members'] = []
44-
for user in User.get_info(uids=list(set(team['members']) - set(team['chiefs']))).values():
59+
for user in User.get_info(
60+
uids=list(set(teamusers.members) - set(teamusers.chiefs))).values():
4561
h_msg = hashlib.md5()
4662
h_msg.update(user['oauth']['email'].encode('utf-8'))
47-
data['members'].append({
63+
data.members.append(Member.parse_obj({
4864
'name': user['profile']['badge_name'],
4965
'email_hash': h_msg.hexdigest(),
50-
})
66+
}))
5167

52-
result.append(data)
68+
result.append(data.dict())
5369

5470
return jsonify({'data': result})

‎view/budget.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def batch(pid):
3333

3434
if data and data.get('casename') == 'get':
3535
teams = [
36-
{'name': team['name'], 'tid': team['tid']}
36+
{'name': team.name, 'tid': team.id}
3737
for team in Team.list_by_pid(pid=project.id)
3838
]
3939

@@ -111,7 +111,7 @@ def by_project_index(pid):
111111
if data['casename'] == 'get':
112112
teams = []
113113
for team in Team.list_by_pid(pid=project.id):
114-
teams.append({'name': team['name'], 'tid': team['tid']})
114+
teams.append({'name': team.name, 'tid': team.id})
115115

116116
default_budget = {
117117
'bid': '',

‎view/project.py

+72-33
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import math
66

77
import arrow
8-
from flask import (Blueprint, g, jsonify, redirect, render_template, request,
9-
url_for)
8+
from flask import (Blueprint, Response, g, jsonify, redirect, render_template,
9+
request, url_for)
1010

1111
import setting
1212
from celery_task.task_service_sync import service_sync_mattermost_add_channel
@@ -67,7 +67,10 @@ def project_edit_create_team(pid):
6767
if g.user['account']['_id'] not in project.owners:
6868
return redirect(url_for('project.team_page', pid=pid, _scheme='https', _external=True))
6969

70-
teams = Team.list_by_pid(project.id, show_all=True)
70+
teams = []
71+
for team in Team.list_by_pid(project.id, show_all=True):
72+
teams.append(team.dict(by_alias=True))
73+
7174
return render_template('./project_edit_create_team.html',
7275
project=project.dict(by_alias=True), teams=teams)
7376

@@ -213,8 +216,13 @@ def project_form_api(pid):
213216
elif data['case'] == 'clothes':
214217
all_users = {}
215218
for team in Team.list_by_pid(pid=pid):
216-
for uid in team['chiefs']+team['members']:
217-
all_users[uid] = {'tid': team['tid']}
219+
if team.chiefs:
220+
for uid in team.chiefs:
221+
all_users[uid] = {'tid': team.id}
222+
223+
if team.members:
224+
for uid in team.members:
225+
all_users[uid] = {'tid': team.id}
218226

219227
user_info = User.get_info(uids=list(all_users.keys()))
220228

@@ -281,8 +289,13 @@ def project_form_api(pid):
281289
elif data['case'] == 'drink':
282290
all_users = {}
283291
for team in Team.list_by_pid(pid=pid):
284-
for uid in team['chiefs']+team['members']:
285-
all_users[uid] = {'tid': team['tid']}
292+
if team.chiefs:
293+
for uid in team.chiefs:
294+
all_users[uid] = {'tid': team.id}
295+
296+
if team.members:
297+
for uid in team.members:
298+
all_users[uid] = {'tid': team.id}
286299

287300
user_info = User.get_info(uids=list(all_users.keys()))
288301

@@ -318,26 +331,29 @@ def project_form_api(pid):
318331

319332

320333
@VIEW_PROJECT.route('/<pid>/edit/team/api', methods=('GET', 'POST'))
321-
def project_edit_create_team_api(pid):
334+
def project_edit_create_team_api(pid: str) -> Response:
322335
''' Project edit create team API '''
323336
project = Project.get(pid)
324-
if g.user['account']['_id'] not in project.owners:
337+
if not project.owners or g.user['account']['_id'] not in project.owners:
325338
return redirect(url_for('project.team_page', pid=pid, _scheme='https', _external=True))
326339

327340
if request.method == 'GET':
328-
_team = Team.get(pid, request.args['tid'].strip())
329-
team = {}
330-
for k in ('name', 'chiefs', 'members', 'owners', 'tid',
331-
'headcount', 'mailling', 'disabled'):
332-
if k in _team:
333-
team[k] = _team[k]
341+
_data = Team.get(pid, request.args['tid'].strip())
342+
if _data is not None:
343+
_team = _data.dict(by_alias=True)
334344

335-
if 'headcount' not in team:
336-
team['headcount'] = 0
337-
else:
338-
team['headcount'] = max([0, int(team['headcount'])])
345+
team = {}
346+
for k in ('name', 'chiefs', 'members', 'owners', 'tid',
347+
'headcount', 'mailling', 'disabled'):
348+
if k in _team:
349+
team[k] = _team[k]
339350

340-
return jsonify(team)
351+
if 'headcount' not in team:
352+
team['headcount'] = 0
353+
else:
354+
team['headcount'] = max([0, int(team['headcount'])])
355+
356+
return jsonify(team)
341357

342358
if request.method == 'POST':
343359
data = request.json
@@ -382,20 +398,28 @@ def team_page(pid):
382398
if not project:
383399
return 'no data', 404
384400

385-
data = list(Team.list_by_pid(project.id))
401+
data = [team.dict(by_alias=True) for team in Team.list_by_pid(project.id)]
386402
uids = []
387403
for team in data:
388-
uids.extend(team['chiefs'])
404+
if team['chiefs']:
405+
uids.extend(team['chiefs'])
389406

390407
total = 0
391408
user_info = User.get_info(uids)
392409
for team in data:
393410
team['chiefs_name'] = []
394-
for uid in team['chiefs']:
395-
team['chiefs_name'].append(
396-
f'''<a href="/user/{uid}">{user_info[uid]['profile']['badge_name']}</a>''')
397-
398-
team['count'] = len(set(team['chiefs'] + team['members']))
411+
if team['chiefs']:
412+
for uid in team['chiefs']:
413+
team['chiefs_name'].append(
414+
f'''<a href="/user/{uid}">{user_info[uid]['profile']['badge_name']}</a>''')
415+
416+
team_uids = set()
417+
if team['chiefs']:
418+
team_uids.update(team['chiefs'])
419+
if team['members']:
420+
team_uids.update(team['members'])
421+
422+
team['count'] = len(team_uids)
399423
total += team['count']
400424

401425
# ----- group for layout ----- #
@@ -460,8 +484,13 @@ def project_form_accommodation(pid):
460484
if post_data['casename'] == 'get':
461485
all_users = {}
462486
for team in Team.list_by_pid(pid=pid):
463-
for uid in team['chiefs']+team['members']:
464-
all_users[uid] = {'tid': team['tid']}
487+
if team.chiefs:
488+
for uid in team.chiefs:
489+
all_users[uid] = {'tid': team.id}
490+
491+
if team.members:
492+
for uid in team.members:
493+
all_users[uid] = {'tid': team.id}
465494

466495
raws = []
467496
for raw in FormAccommodation.get(pid):
@@ -518,8 +547,13 @@ def project_dietary_habit(pid):
518547
if post_data['casename'] == 'get':
519548
all_users = {}
520549
for team in Team.list_by_pid(pid=pid):
521-
for uid in team['chiefs']+team['members']:
522-
all_users[uid] = {'tid': team['tid']}
550+
if team.chiefs:
551+
for uid in team.chiefs:
552+
all_users[uid] = {'tid': team.id}
553+
554+
if team.members:
555+
for uid in team.members:
556+
all_users[uid] = {'tid': team.id}
523557

524558
user_infos = User.get_info(
525559
uids=list(all_users.keys()), need_sensitive=True)
@@ -556,8 +590,13 @@ def project_contact_book(pid):
556590
if post_data['casename'] == 'get':
557591
all_users = {}
558592
for team in Team.list_by_pid(pid=pid):
559-
for uid in team['chiefs']+team['members']:
560-
all_users[uid] = {'tid': team['tid']}
593+
if team.chiefs:
594+
for uid in team.chiefs:
595+
all_users[uid] = {'tid': team.id}
596+
597+
if team.members:
598+
for uid in team.members:
599+
all_users[uid] = {'tid': team.id}
561600

562601
user_infos = User.get_info(
563602
uids=list(all_users.keys()), need_sensitive=True)

‎view/recruit.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from module.skill import (RecruitQuery, SkillEnum, SkillEnumDesc, StatusEnum,
55
StatusEnumDesc, TeamsEnum, TeamsEnumDesc)
66
from module.users import TobeVolunteer, User
7+
from structs.teams import TeamUsers
78
from view.utils import check_the_team_and_project_are_existed
89

910
VIEW_RECRUIT = Blueprint('recruit', __name__, url_prefix='/recruit')
@@ -14,19 +15,21 @@ def recurit_list(pid, tid):
1415
''' List page '''
1516
team, project, _redirect = check_the_team_and_project_are_existed(
1617
pid=pid, tid=tid)
17-
if _redirect:
18+
if team is None or project is None or _redirect:
1819
return _redirect
1920

20-
is_admin = (g.user['account']['_id'] in team['chiefs'] or
21-
g.user['account']['_id'] in team['owners'] or
21+
teamusers = TeamUsers.parse_obj(team)
22+
is_admin = (g.user['account']['_id'] in teamusers.chiefs or
23+
g.user['account']['_id'] in teamusers.owners or
2224
g.user['account']['_id'] in project.owners)
2325

2426
if not is_admin:
2527
return redirect('/')
2628

2729
if request.method == 'GET':
2830
return render_template('./recruit_list.html',
29-
project=project, team=team, is_admin=is_admin)
31+
project=project.dict(by_alias=True),
32+
team=team.dict(by_alias=True), is_admin=is_admin)
3033

3134
if request.method == 'POST':
3235
post_data = request.get_json()

‎view/sender.py

+66-56
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from module.sender import SenderCampaign, SenderLogs, SenderReceiver
1111
from module.team import Team
1212
from module.users import User
13+
from structs.teams import TeamUsers
1314
from view.utils import check_the_team_and_project_are_existed
1415

1516
VIEW_SENDER = Blueprint('sender', __name__, url_prefix='/sender')
@@ -20,11 +21,12 @@ def index(pid, tid):
2021
''' Index page '''
2122
team, project, _redirect = check_the_team_and_project_are_existed(
2223
pid=pid, tid=tid)
23-
if _redirect:
24+
if team is None or project is None or _redirect:
2425
return _redirect
2526

26-
is_admin = (g.user['account']['_id'] in team['chiefs'] or
27-
g.user['account']['_id'] in team['owners'] or
27+
teamusers = TeamUsers.parse_obj(team)
28+
is_admin = (g.user['account']['_id'] in teamusers.chiefs or
29+
g.user['account']['_id'] in teamusers.owners or
2830
g.user['account']['_id'] in project.owners)
2931

3032
if not is_admin:
@@ -44,7 +46,7 @@ def index(pid, tid):
4446

4547
if 'casename' in data and data['casename'] == 'get':
4648
campaigns = list(SenderCampaign.get_list(
47-
pid=team['pid'], tid=team['tid']))
49+
pid=team.pid, tid=team.id))
4850
raw_users_info = User.get_info(
4951
uids=[c['created']['uid'] for c in campaigns])
5052
users_info = {}
@@ -56,7 +58,7 @@ def index(pid, tid):
5658

5759
if 'casename' in data and data['casename'] == 'create':
5860
resp = SenderCampaign.create(
59-
name=data['name'], pid=team['pid'], tid=team['tid'], uid=g.user['account']['_id'])
61+
name=data['name'], pid=team.pid, tid=team.id, uid=g.user['account']['_id'])
6062

6163
return jsonify({'cid': resp['_id']})
6264

@@ -68,48 +70,52 @@ def campaign(pid, tid, cid):
6870
''' campaign '''
6971
team, project, _redirect = check_the_team_and_project_are_existed(
7072
pid=pid, tid=tid)
71-
if _redirect:
73+
if team is None or project is None or _redirect:
7274
return _redirect
7375

74-
is_admin = (g.user['account']['_id'] in team['chiefs'] or
75-
g.user['account']['_id'] in team['owners'] or
76+
teamusers = TeamUsers.parse_obj(team)
77+
is_admin = (g.user['account']['_id'] in teamusers.chiefs or
78+
g.user['account']['_id'] in teamusers.owners or
7679
g.user['account']['_id'] in project.owners)
7780

7881
if not is_admin:
7982
return redirect('/')
8083

8184
campaign_data = SenderCampaign.get(cid=cid, pid=pid, tid=tid)
8285

83-
return render_template('./sender_campaign_index.html', campaign=campaign_data, team=team)
86+
return render_template('./sender_campaign_index.html',
87+
campaign=campaign_data, team=team.dict(by_alias=True))
8488

8589

8690
@VIEW_SENDER.route('/<pid>/<tid>/campaign/<cid>/content', methods=('GET', 'POST'))
8791
def campaign_content(pid, tid, cid):
8892
''' Campaign content '''
8993
team, project, _redirect = check_the_team_and_project_are_existed(
9094
pid=pid, tid=tid)
91-
if _redirect:
95+
if team is None or project is None or _redirect:
9296
return _redirect
9397

94-
is_admin = (g.user['account']['_id'] in team['chiefs'] or
95-
g.user['account']['_id'] in team['owners'] or
98+
teamusers = TeamUsers.parse_obj(team)
99+
is_admin = (g.user['account']['_id'] in teamusers.chiefs or
100+
g.user['account']['_id'] in teamusers.owners or
96101
g.user['account']['_id'] in project.owners)
97102

98103
if not is_admin:
99104
return redirect('/')
100105

101106
if request.method == 'GET':
102107
campaign_data = SenderCampaign.get(
103-
cid=cid, pid=team['pid'], tid=team['tid'])
108+
cid=cid, pid=team.pid, tid=team.id)
104109

105-
return render_template('./sender_campaign_content.html', campaign=campaign_data, team=team)
110+
return render_template('./sender_campaign_content.html',
111+
campaign=campaign_data, team=team.dict(by_alias=True))
106112

107113
if request.method == 'POST':
108114
data = request.get_json()
109115

110116
if 'casename' in data and data['casename'] == 'get':
111117
campaign_data = SenderCampaign.get(
112-
cid=cid, pid=team['pid'], tid=team['tid'])
118+
cid=cid, pid=team.pid, tid=team.id)
113119
return jsonify({'mail': campaign_data['mail']})
114120

115121
if 'casename' in data and data['casename'] == 'save':
@@ -131,20 +137,22 @@ def campaign_receiver(pid, tid, cid):
131137
# pylint: disable=too-many-branches,too-many-locals,too-many-return-statements
132138
team, project, _redirect = check_the_team_and_project_are_existed(
133139
pid=pid, tid=tid)
134-
if _redirect:
140+
if team is None or project is None or _redirect:
135141
return _redirect
136142

137-
is_admin = (g.user['account']['_id'] in team['chiefs'] or
138-
g.user['account']['_id'] in team['owners'] or
143+
teamusers = TeamUsers.parse_obj(team)
144+
is_admin = (g.user['account']['_id'] in teamusers.chiefs or
145+
g.user['account']['_id'] in teamusers.owners or
139146
g.user['account']['_id'] in project.owners)
140147

141148
if not is_admin:
142149
return redirect('/')
143150

144151
campaign_data = SenderCampaign.get(
145-
cid=cid, pid=team['pid'], tid=team['tid'])
152+
cid=cid, pid=team.pid, tid=team.id)
146153
if request.method == 'GET':
147-
return render_template('./sender_campaign_receiver.html', campaign=campaign_data, team=team)
154+
return render_template('./sender_campaign_receiver.html',
155+
campaign=campaign_data, team=team.dict(by_alias=True))
148156

149157
if request.method == 'POST':
150158
data = {}
@@ -154,19 +162,19 @@ def campaign_receiver(pid, tid, cid):
154162

155163
if data and 'casename' in data and data['casename'] == 'getinit':
156164
teams = []
157-
for _team in Team.list_by_pid(pid=team['pid']):
158-
teams.append({'tid': _team['tid'], 'name': _team['name']})
165+
for _team in Team.list_by_pid(pid=team.pid):
166+
teams.append({'tid': _team.id, 'name': _team.name})
159167

160168
team_w_tags = []
161169
if 'tag_members' in team:
162170
team_w_tags = team['tag_members']
163171

164-
sender_receiver = SenderReceiver.get(pid=team['pid'], cid=cid)
172+
sender_receiver = SenderReceiver.get(pid=team.pid, cid=cid)
165173

166174
picktags = []
167175
if 'team_w_tags' in campaign_data['receiver'] and \
168-
team['tid'] in campaign_data['receiver']['team_w_tags']:
169-
picktags = campaign_data['receiver']['team_w_tags'][team['tid']]
176+
team.id in campaign_data['receiver']['team_w_tags']:
177+
picktags = campaign_data['receiver']['team_w_tags'][team.id]
170178

171179
return jsonify({'teams': teams,
172180
'team_w_tags': team_w_tags,
@@ -178,26 +186,26 @@ def campaign_receiver(pid, tid, cid):
178186
})
179187

180188
if data and 'casename' in data and data['casename'] == 'save':
181-
tids = [team['tid'] for team in Team.list_by_pid(pid=team['pid'])]
189+
tids = [team.id for team in Team.list_by_pid(pid=team.pid)]
182190

183191
_result = []
184192
for tid_info in tids:
185193
if tid_info in data['pickteams']:
186194
_result.append(tid_info)
187195

188196
_team_w_tags = []
189-
if 'tag_members' in team:
190-
for tag in team['tag_members']:
191-
if tag['id'] in data['picktags']:
192-
_team_w_tags.append(tag['id'])
197+
if team.tag_members:
198+
for tag in team.tag_members:
199+
if tag.id in data['picktags']:
200+
_team_w_tags.append(tag.id)
193201

194202
return jsonify(SenderCampaign.save_receiver(
195203
cid=cid, teams=_result, team_w_tags={
196-
team['tid']: _team_w_tags},
204+
team.id: _team_w_tags},
197205
all_users=bool(data['is_all_users']))['receiver'])
198206

199207
if request.form['uploadtype'] == 'remove':
200-
SenderReceiver.remove(pid=team['pid'], cid=cid)
208+
SenderReceiver.remove(pid=team.pid, cid=cid)
201209

202210
return jsonify({'file': [],
203211
'uploadtype': request.form['uploadtype'],
@@ -208,9 +216,9 @@ def campaign_receiver(pid, tid, cid):
208216
request.files['file'].read().decode('utf8'))))
209217
if request.form['uploadtype'] == 'replace':
210218
SenderReceiver.replace(
211-
pid=team['pid'], cid=cid, datas=csv_file)
219+
pid=team.pid, cid=cid, datas=csv_file)
212220
elif request.form['uploadtype'] == 'update':
213-
SenderReceiver.update(pid=team['pid'], cid=cid, datas=csv_file)
221+
SenderReceiver.update(pid=team.pid, cid=cid, datas=csv_file)
214222

215223
return jsonify({'file': csv_file,
216224
'uploadtype': request.form['uploadtype'],
@@ -225,20 +233,22 @@ def campaign_schedule(pid, tid, cid):
225233
# pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-return-statements
226234
team, project, _redirect = check_the_team_and_project_are_existed(
227235
pid=pid, tid=tid)
228-
if _redirect:
236+
if team is None or project is None or _redirect:
229237
return _redirect
230238

231-
is_admin = (g.user['account']['_id'] in team['chiefs'] or
232-
g.user['account']['_id'] in team['owners'] or
239+
teamusers = TeamUsers.parse_obj(team)
240+
is_admin = (g.user['account']['_id'] in teamusers.chiefs or
241+
g.user['account']['_id'] in teamusers.owners or
233242
g.user['account']['_id'] in project.owners)
234243

235244
if not is_admin:
236245
return redirect('/')
237246

238247
campaign_data = SenderCampaign.get(
239-
cid=cid, pid=team['pid'], tid=team['tid'])
248+
cid=cid, pid=team.pid, tid=team.id)
240249
if request.method == 'GET':
241-
return render_template('./sender_campaign_schedule.html', campaign=campaign_data, team=team)
250+
return render_template('./sender_campaign_schedule.html',
251+
campaign=campaign_data, team=team.dict(by_alias=True))
242252

243253
if request.method == 'POST':
244254
data = request.get_json()
@@ -265,16 +275,16 @@ def campaign_schedule(pid, tid, cid):
265275
for raw in raws:
266276
user_datas.append(dict(zip(fields, raw)))
267277

268-
fields, raws = SenderReceiver.get(pid=team['pid'], cid=cid)
278+
fields, raws = SenderReceiver.get(pid=team.pid, cid=cid)
269279
for raw in raws:
270280
user_datas.append(dict(zip(fields, raw)))
271281

272282
if 'team_w_tags' in campaign_data['receiver'] and \
273-
team['tid'] in campaign_data['receiver']['team_w_tags'] and \
274-
campaign_data['receiver']['team_w_tags'][team['tid']]:
283+
team.id in campaign_data['receiver']['team_w_tags'] and \
284+
campaign_data['receiver']['team_w_tags'][team.id]:
275285
fields, raws = SenderReceiver.get_by_tags(
276-
pid=team['pid'], tid=team['tid'],
277-
tags=campaign_data['receiver']['team_w_tags'][team['tid']])
286+
pid=team.pid, tid=team.id,
287+
tags=campaign_data['receiver']['team_w_tags'][team.id])
278288

279289
for raw in raws:
280290
user_datas.append(dict(zip(fields, raw)))
@@ -290,8 +300,8 @@ def campaign_schedule(pid, tid, cid):
290300

291301
source = None
292302
if campaign_data['mail']['layout'] == '2':
293-
if 'mailling' in team and team['mailling']:
294-
source = {'name': team['name'], 'mail': team['mailling']}
303+
if team.mailling:
304+
source = {'name': team.name, 'mail': team.mailling}
295305
if not (source['name'].startswith('COSCUP') or
296306
source['name'].startswith('coscup')):
297307
source['name'] = f"COSCUP {source['name']}"
@@ -300,7 +310,7 @@ def campaign_schedule(pid, tid, cid):
300310
'mail': 'attendee@coscup.org'}
301311

302312
sender_mailer_start.apply_async(kwargs={
303-
'campaign_data': campaign_data, 'team_name': team['name'], 'source': source,
313+
'campaign_data': campaign_data, 'team_name': team.name, 'source': source,
304314
'user_datas': user_datas, 'layout': campaign_data['mail']['layout']})
305315

306316
return jsonify(data)
@@ -310,20 +320,20 @@ def campaign_schedule(pid, tid, cid):
310320
user_datas = []
311321

312322
fields, raws = SenderReceiver.get_from_user(
313-
pid=team['pid'], tids=campaign_data['receiver']['teams'])
323+
pid=team.pid, tids=campaign_data['receiver']['teams'])
314324
if raws:
315325
user_datas.append(dict(zip(fields, random.choice(raws))))
316326

317-
fields, raws = SenderReceiver.get(pid=team['pid'], cid=cid)
327+
fields, raws = SenderReceiver.get(pid=team.pid, cid=cid)
318328
if raws:
319329
user_datas.append(dict(zip(fields, random.choice(raws))))
320330

321331
if 'team_w_tags' in campaign_data['receiver'] and \
322-
team['tid'] in campaign_data['receiver']['team_w_tags'] and \
323-
campaign_data['receiver']['team_w_tags'][team['tid']]:
332+
team.id in campaign_data['receiver']['team_w_tags'] and \
333+
campaign_data['receiver']['team_w_tags'][team.id]:
324334
fields, raws = SenderReceiver.get_by_tags(
325-
pid=team['pid'], tid=team['tid'],
326-
tags=campaign_data['receiver']['team_w_tags'][team['tid']])
335+
pid=team.pid, tid=team.id,
336+
tags=campaign_data['receiver']['team_w_tags'][team.id])
327337
if raws:
328338
user_datas.append(dict(zip(fields, random.choice(raws))))
329339

@@ -346,8 +356,8 @@ def campaign_schedule(pid, tid, cid):
346356

347357
source = None
348358
if campaign_data['mail']['layout'] == '2':
349-
if 'mailling' in team and team['mailling']:
350-
source = {'name': team['name'], 'mail': team['mailling']}
359+
if team.mailling:
360+
source = {'name': team.name, 'mail': team.mailling}
351361
if not (source['name'].startswith('COSCUP') or
352362
source['name'].startswith('coscup')):
353363
source['name'] = f"COSCUP {source['name']}"
@@ -356,7 +366,7 @@ def campaign_schedule(pid, tid, cid):
356366
'mail': 'attendee@coscup.org'}
357367

358368
sender_mailer_start.apply_async(kwargs={
359-
'campaign_data': campaign_data, 'team_name': team['name'], 'source': source,
369+
'campaign_data': campaign_data, 'team_name': team.name, 'source': source,
360370
'user_datas': user_datas, 'layout': campaign_data['mail']['layout']})
361371

362372
return jsonify(data)

‎view/team.py

+173-119
Large diffs are not rendered by default.

‎view/utils.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
''' utils '''
2-
from flask import redirect
2+
from flask import Response, redirect
33

44
from module.project import Project
55
from module.team import Team
6+
from structs.projects import ProjectBase
7+
from structs.teams import TeamBase
68

79

8-
def check_the_team_and_project_are_existed(pid, tid):
10+
def check_the_team_and_project_are_existed(pid: str, tid: str) -> \
11+
tuple[TeamBase | None, ProjectBase | None, Response | None]:
912
''' Base check the team and profect are existed
1013
1114
:param str pid: project id

0 commit comments

Comments
 (0)
Please sign in to comment.