1+ import collections
12import json
2- import requests
3+ import os
34
45from flask import Blueprint
56from flask import request
67from flask import jsonify
78from flask import redirect
89
9- from sqlalchemy import desc
10-
10+ import requests
11+ import boto3
1112import structlog
1213
13- from conditional . util . context_processors import get_member_name
14+ from werkzeug . utils import secure_filename
1415
16+ from conditional import db , get_user , auth , app
1517from conditional .models .models import MajorProject
18+ from conditional .models .models import MajorProjectSkill
1619
17- from conditional .util .ldap import ldap_is_eval_director
20+ from conditional .util .context_processors import get_member_name
1821from conditional .util .ldap import ldap_get_member
1922from conditional .util .flask import render_template
23+ from conditional .util .s3 import list_files_in_folder
24+ from conditional .util .user_dict import user_dict_is_eval_director
25+ from conditional .util .major_project import get_project_list
2026
21- from conditional import db , start_of_year , get_user , auth , app
27+ collections . Callable = collections . abc . Callable
2228
2329logger = structlog .get_logger ()
2430
2531major_project_bp = Blueprint ("major_project_bp" , __name__ )
2632
27-
2833@major_project_bp .route ("/major_project/" )
2934@auth .oidc_auth ("default" )
3035@get_user
3136def display_major_project (user_dict = None ):
3237 log = logger .new (request = request , auth_dict = user_dict )
3338 log .info ("Display Major Project Page" )
3439
35- major_projects = [
40+ # There is probably a better way to do this, but it does work
41+ proj_list : list = get_project_list ()
42+
43+ bucket : str = app .config ['S3_BUCKET_ID' ]
44+
45+ major_projects : list [dict ] = [
3646 {
47+ "id" : p .id ,
48+ "date" : p .date ,
3749 "username" : p .uid ,
3850 "name" : ldap_get_member (p .uid ).cn ,
3951 "proj_name" : p .name ,
52+ "tldr" : p .tldr ,
53+ "time_spent" : p .time_spent ,
54+ "skills" : p .skills ,
55+ "desc" : p .description ,
56+ "links" : list (filter (None , p .links .split ("\n " ))),
4057 "status" : p .status ,
41- "description" : p .description ,
42- "id" : p .id ,
4358 "is_owner" : bool (user_dict ["username" ] == p .uid ),
59+ "files" : list_files_in_folder (bucket , f"{ p .id } /" )
4460 }
45- for p in MajorProject .query .filter (
46- MajorProject .date > start_of_year ()
47- ).order_by (desc (MajorProject .id ))
61+ for p in proj_list
4862 ]
4963
50- major_projects_len = len (major_projects )
5164 # return names in 'first last (username)' format
5265 return render_template (
5366 "major_project_submission.html" ,
5467 major_projects = major_projects ,
55- major_projects_len = major_projects_len ,
56- username = user_dict ["username" ],
57- )
68+ major_projects_len = len (major_projects ),
69+ username = user_dict ["username" ])
70+
71+ @major_project_bp .route ("/major_project/upload" , methods = ["POST" ])
72+ @auth .oidc_auth ("default" )
73+ @get_user
74+ def upload_major_project_files (user_dict = None ):
75+ log = logger .new (request = request , auth_dict = user_dict )
76+ log .info ('Uploading Major Project File(s)' )
77+
78+ if len (list (request .files .keys ())) < 1 :
79+ return "No file" , 400
80+
81+ # Temporarily save files to a place, to be uploaded on submit
82+ for _ , file in request .files .lists ():
83+ file = file [0 ]
84+ safe_name : str = secure_filename (file .filename )
85+ filename = f"/tmp/{ user_dict ['username' ]} /{ safe_name } "
86+
87+ os .makedirs (os .path .dirname (filename ), exist_ok = True )
88+ file .save (filename )
89+
90+ return jsonify ({"success" : True }), 200
91+
5892
5993
6094@major_project_bp .route ("/major_project/submit" , methods = ["POST" ])
@@ -65,27 +99,83 @@ def submit_major_project(user_dict=None):
6599 log .info ("Submit Major Project" )
66100
67101 post_data = request .get_json ()
102+
68103 name = post_data ["projectName" ]
104+ tldr = post_data ['projectTldr' ]
105+ time_spent = post_data ['projectTimeSpent' ]
106+ skills = post_data ['projectSkills' ]
69107 description = post_data ["projectDescription" ]
108+ links = post_data ['projectLinks' ]
70109
71- if name == "" or description == "" :
110+ user_id = user_dict ['username' ]
111+
112+ log .info (user_id )
113+
114+ # All fields are required in order to be able to submit the form
115+ if not name or not tldr or not time_spent or not description :
72116 return jsonify ({"success" : False }), 400
73- project = MajorProject (user_dict ["username" ], name , description )
74117
75- # Don't you dare try pinging @channel
76- name = name .replace ("<!" , "<! " )
118+ project : MajorProject = MajorProject (user_id , name , tldr , time_spent , description , links )
77119
78- username = user_dict ["username" ]
79- send_slack_ping (
80- {
81- "text" : f"<!subteam^S5XENJJAH> *{ get_member_name (username )} * ({ username } )"
82- f" submitted their major project, *{ name } *! Please be sure to reach out"
83- f" to E-Board members to answer any questions they may have regarding"
84- f" your project!"
85- }
86- )
120+ # Save the info to the database
87121 db .session .add (project )
88122 db .session .commit ()
123+
124+ project = MajorProject .query .filter (
125+ MajorProject .name == name ,
126+ MajorProject .uid == user_id
127+ ).first ()
128+
129+ skills_list : list = list (filter (lambda x : x != 'None' , skills ))
130+
131+ for skill in skills_list :
132+ skill = skill .strip ()
133+
134+ if skill not in ("" , 'None' ):
135+ mp_skill = MajorProjectSkill (project .id , skill )
136+ db .session .add (mp_skill )
137+
138+ db .session .commit ()
139+
140+ # Fail if attempting to retreive non-existent project
141+ if project is None :
142+ return jsonify ({"success" : False }), 500
143+
144+ # Sanitize input so that the Slackbot cannot ping @channel
145+ name = name .replace ("<!" , "<! " )
146+
147+ # Connect to S3 bucket
148+ s3 = boto3 .client ("s3" ,
149+ aws_access_key_id = app .config ['AWS_ACCESS_KEY_ID' ],
150+ aws_secret_access_key = app .config ['AWS_SECRET_ACCESS_KEY' ],
151+ endpoint_url = app .config ['S3_URI' ])
152+
153+ # Collect all the locally cached files and put them in the bucket
154+ temp_dir : str = f"/tmp/{ user_id } "
155+ if os .path .exists (temp_dir ):
156+ for file in os .listdir (temp_dir ):
157+ filepath = f"{ temp_dir } /{ file } "
158+
159+ s3 .upload_file (filepath , 'major-project-media' , f"{ project .id } /{ file } " )
160+
161+ os .remove (filepath )
162+
163+ # Delete the temp directory once all the files have been stored in S3
164+ os .rmdir (temp_dir )
165+
166+
167+ # Send the slack ping only after we know that the data was properly saved to the DB
168+ if app .config ['DEV_DISABLE_SLACK_PING' ]:
169+ log .info ("Slack ping skipped due to environment override" )
170+ else :
171+ send_slack_ping (
172+ {
173+ "text" : f"<!subteam^S5XENJJAH> *{ get_member_name (user_id )} * ({ user_id } )"
174+ f" submitted their major project, *{ name } *!"
175+ }
176+ )
177+
178+
89179 return jsonify ({"success" : True }), 200
90180
91181
@@ -95,7 +185,7 @@ def submit_major_project(user_dict=None):
95185def major_project_review (user_dict = None ):
96186 log = logger .new (request = request , auth_dict = user_dict )
97187
98- if not ldap_is_eval_director (user_dict ["account" ]):
188+ if not user_dict_is_eval_director (user_dict ["account" ]):
99189 return redirect ("/dashboard" , code = 302 )
100190
101191 post_data = request .get_json ()
@@ -106,8 +196,10 @@ def major_project_review(user_dict=None):
106196
107197 print (post_data )
108198 MajorProject .query .filter (MajorProject .id == pid ).update ({"status" : status })
199+
109200 db .session .flush ()
110201 db .session .commit ()
202+
111203 return jsonify ({"success" : True }), 200
112204
113205
@@ -121,10 +213,12 @@ def major_project_delete(pid, user_dict=None):
121213 major_project = MajorProject .query .filter (MajorProject .id == pid ).first ()
122214 creator = major_project .uid
123215
124- if creator == user_dict ["username" ] or ldap_is_eval_director (user_dict ["account" ]):
216+ if creator == user_dict ["username" ] or user_dict_is_eval_director (user_dict ["account" ]):
125217 MajorProject .query .filter (MajorProject .id == pid ).delete ()
218+
126219 db .session .flush ()
127220 db .session .commit ()
221+
128222 return jsonify ({"success" : True }), 200
129223
130224 return "Must be project owner to delete!" , 401
0 commit comments