Skip to content

Commit f554fe6

Browse files
committed
feat: backup import
1 parent 2cf6598 commit f554fe6

File tree

5 files changed

+107
-63
lines changed

5 files changed

+107
-63
lines changed

main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
groups_router,
88
trophies_router,
99
plugins_router,
10-
exports_router
10+
exports_router,
11+
imports_router
1112
)
1213
from database import close_mongo_connection, connect_to_mongo
1314
import socketio
@@ -62,6 +63,7 @@ async def mark_all_tasks_failed():
6263
app.include_router(trophies_router.router, prefix="/api/trophy", tags=["trophies"])
6364
app.include_router(plugins_router.router, prefix='/api/plugin', tags=['plugins', 'calculator', 'dictionary'])
6465
app.include_router(exports_router.router, prefix='/api/exports', tags=['exports'])
66+
app.include_router(imports_router.router, prefix='/api/imports', tags=['imports'])
6567

6668
@app.router.get("/api/")
6769
async def home():
@@ -78,7 +80,8 @@ async def home():
7880
"group": "/api/group",
7981
"trophy": "/api/trophy",
8082
"plugin": "/api/plugin",
81-
"exports": "/api/exports"
83+
"exports": "/api/exports",
84+
"imports": "/api/imports"
8285
}
8386
}}
8487

routers/activities_router.py

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import re
2-
from io import BytesIO
32
from typings.activity import (
43
Activity,
54
ActivityMember,
6-
ActivityMode,
75
ActivityStatus,
86
ActivityType,
97
MemberActivityStatus,
10-
SpecialActivityClassify, Special,
8+
SpecialActivityClassify,
119
)
12-
from fastapi import APIRouter, File, HTTPException, Depends, UploadFile
13-
import copy
10+
from fastapi import APIRouter, HTTPException, Depends
1411
from typings.log import inject_log
1512
from util.get_class import get_activities_related_to_user
1613
from util.group import is_in_a_same_class
@@ -19,7 +16,6 @@
1916
from datetime import datetime
2017
from database import db
2118
from pydantic import BaseModel
22-
import pandas as pd
2319

2420
router = APIRouter()
2521

@@ -91,57 +87,6 @@ async def create_activity(payload: Activity, user=Depends(get_current_user), log
9187
return {"status": "ok", "code": 201, "data": str(id)}
9288

9389

94-
@router.post("/upload")
95-
async def upload_activity_excel(name: str, desc: str, payload: UploadFile = File(...), user=Depends(get_current_user),
96-
log=Depends(inject_log)):
97-
"""
98-
Upload activity excel
99-
"""
100-
101-
expected_columns = ['_id', 'ID', 'Name', 'Class ID', 'On Campus', 'Off Campus', 'Social Practice']
102-
103-
try:
104-
contents = await payload.read()
105-
filename = BytesIO(contents)
106-
107-
sheet_names = pd.ExcelFile(filename).sheet_names
108-
109-
# Read all sheets into a list of DataFrames
110-
dfs = [pd.read_excel(filename, sheet_name=sheet) for sheet in sheet_names]
111-
112-
# Concatenate all DataFrames into one
113-
df = pd.concat(dfs, ignore_index=True)
114-
115-
if df.columns.to_list() != expected_columns:
116-
raise HTTPException(status_code=400, detail="Invalid excel format")
117-
118-
df.fillna(0.0)
119-
accepted_modes = ['On Campus', 'Off Campus', 'Social Practice']
120-
121-
info = Activity(_id='', type=ActivityType.special, name=name, description=desc, members=[], registration=None,
122-
date=datetime.now().isoformat(), createdAt=datetime.now().isoformat(),
123-
updatedAt=datetime.now().isoformat(), creator=user['id'], status=ActivityStatus.effective,
124-
special=Special(classify=SpecialActivityClassify.import_), approver='authority')
125-
126-
for mode in accepted_modes:
127-
template = copy.deepcopy(info)
128-
template.name += ' | Mode: ' + mode
129-
for idx, row in df.iterrows():
130-
if row[mode] != 0.0 and not pd.isna(row[mode]):
131-
template.members.append(ActivityMember(_id=row['_id'], id=row['_id'], status=MemberActivityStatus.effective,
132-
mode=ActivityMode(mode.replace(' ', '-').lower()),
133-
duration=row[mode]))
134-
if len(user) != 0:
135-
await create_activity(template, user=user, log=log)
136-
except Exception as e:
137-
raise HTTPException(status_code=400, detail=str(e))
138-
139-
log.with_text(f"User {await get_user_name(user['id'])} uploaded activity excel")
140-
await log.insert_log()
141-
142-
return {"status": "ok", "code": 201}
143-
144-
14590
class PutDescription(BaseModel):
14691
description: str
14792

routers/imports_router.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from io import BytesIO
2+
from bson import Binary
3+
4+
from routers.activities_router import create_activity
5+
from typings.activity import (
6+
Activity,
7+
ActivityMember,
8+
ActivityMode,
9+
ActivityStatus,
10+
ActivityType,
11+
MemberActivityStatus,
12+
SpecialActivityClassify, Special,
13+
)
14+
from fastapi import APIRouter, File, HTTPException, Depends, UploadFile
15+
import copy
16+
from typings.log import inject_log
17+
from util.user import get_user_name
18+
from util.object_id import get_current_user
19+
from datetime import datetime
20+
from database import db
21+
import pandas as pd
22+
23+
router = APIRouter()
24+
25+
@router.post("/activity")
26+
async def upload_activity_excel(name: str, desc: str, payload: UploadFile = File(...), user=Depends(get_current_user),
27+
log=Depends(inject_log)):
28+
"""
29+
Upload activity excel
30+
"""
31+
32+
expected_columns = ['_id', 'ID', 'Name', 'Class ID', 'On Campus', 'Off Campus', 'Social Practice']
33+
34+
try:
35+
contents = await payload.read()
36+
37+
await db.zvms.imports.insert_one({
38+
"name": name,
39+
"filename": payload.filename,
40+
"description": desc,
41+
"user": user['id'],
42+
"date": datetime.now().isoformat(),
43+
"content": Binary(contents)
44+
})
45+
46+
filename = BytesIO(contents)
47+
48+
sheet_names = pd.ExcelFile(filename).sheet_names
49+
50+
# Read all sheets into a list of DataFrames
51+
dfs = [pd.read_excel(filename, sheet_name=sheet) for sheet in sheet_names]
52+
53+
# Concatenate all DataFrames into one
54+
df = pd.concat(dfs, ignore_index=True)
55+
56+
if df.columns.to_list() != expected_columns:
57+
raise HTTPException(status_code=400, detail="Invalid excel format")
58+
59+
df.fillna(0.0)
60+
accepted_modes = ['On Campus', 'Off Campus', 'Social Practice']
61+
62+
info = Activity(_id='', type=ActivityType.special, name=name, description=desc, members=[], registration=None,
63+
date=datetime.now().isoformat(), createdAt=datetime.now().isoformat(),
64+
updatedAt=datetime.now().isoformat(), creator=user['id'], status=ActivityStatus.effective,
65+
special=Special(classify=SpecialActivityClassify.import_), approver='authority')
66+
67+
for mode in accepted_modes:
68+
template = copy.deepcopy(info)
69+
template.name += ' | Mode: ' + mode
70+
for idx, row in df.iterrows():
71+
if row[mode] != 0.0 and not pd.isna(row[mode]):
72+
template.members.append(ActivityMember(_id=row['_id'], id=row['_id'], status=MemberActivityStatus.effective,
73+
mode=ActivityMode(mode.replace(' ', '-').lower()),
74+
duration=row[mode]))
75+
if len(user) != 0:
76+
await create_activity(template, user=user, log=log)
77+
except Exception as e:
78+
raise HTTPException(status_code=400, detail=str(e))
79+
80+
log.with_text(f"User {await get_user_name(user['id'])} uploaded activity excel")
81+
await log.insert_log()
82+
83+
return {"status": "ok", "code": 201}

routers/users_router.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ async def auth_user(auth: AuthUser, request: Request, meta=Depends(binding_user_
3838
log = ZVMSLog(str(request.url), auth.id, meta['clarity_id'], f'''User {await get_user_name(id)} is logging in''',
3939
meta['ip'], datetime.timestamp(datetime.now()))
4040

41-
if not log.includes_clarity:
42-
raise HTTPException(status_code=400,
43-
detail=f'''Outdated frontend version. You should refresh pages until a prompt appears, or reinstall the browser.''')
44-
4541
await log.insert_log()
4642

4743
if mode is None:
@@ -177,6 +173,7 @@ async def read_users(query: str = '', page: int = 1, perpage: int = 5, privilege
177173
"$or": [
178174
{"name": {"$regex": query, "$options": "i"}},
179175
{"id": {"$regex": query, "$options": "i"}},
176+
{"past": {"$elemMatch": {"$regex": query, "$options": "i"}}}
180177
] if user is not None else [
181178
# should not search if not login.
182179
{"id": query}
@@ -189,6 +186,7 @@ async def read_users(query: str = '', page: int = 1, perpage: int = 5, privilege
189186
"$or": [
190187
{"name": {"$regex": query, "$options": "i"}},
191188
{"id": {"$regex": query, "$options": "i"}},
189+
{"past": {"$elemMatch": {"$regex": query, "$options": "i"}}}
192190
] if user is not None else [
193191
# should not search if not login.
194192
{"id": query}
@@ -250,6 +248,15 @@ async def update_user(user_oid: str, user_struct: PutUser, user=Depends(compulso
250248
if "admin" not in user["per"]:
251249
raise HTTPException(status_code=403, detail="Permission denied")
252250

251+
user_info = await db.zvms.users.find_one({"_id": validate_object_id(user_oid)})
252+
253+
pasts = []
254+
255+
if user_info['name'] != user_struct.name:
256+
pasts.append(user_info['name'])
257+
if user_info['id'] != user_struct.id:
258+
pasts.append(user_info['id'])
259+
253260
# Update user's information
254261
await db.zvms.users.update_one(
255262
{"_id": validate_object_id(user_oid)},
@@ -258,6 +265,11 @@ async def update_user(user_oid: str, user_struct: PutUser, user=Depends(compulso
258265
"name": user_struct.name,
259266
"id": user_struct.id,
260267
"group": user_struct.group,
268+
},
269+
"$push": {
270+
"past": {
271+
"$each": pasts
272+
}
261273
}
262274
},
263275
)

typings/user.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class User(BaseModel):
3535
name: str
3636
sex: UserSex
3737
group: list[str]
38+
past: list[str]
3839
eligibility: list[UserEligibility] = field(default_factory=list)
3940

4041

0 commit comments

Comments
 (0)