Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ ENV PORT=${PORT}
EXPOSE ${PORT}

COPY conditional /opt/conditional/conditional
COPY *.py package.json /opt/conditional
COPY --from=build-frontend /opt/conditional/conditional/static /opt/conditional/conditional/static
COPY *.py package.json /opt/conditional/
COPY --from=build-frontend /opt/conditional/conditional/static/ /opt/conditional/conditional/static/

RUN ln -sf /usr/share/zoneinfo/America/New_York /etc/localtime

Expand Down
24 changes: 16 additions & 8 deletions conditional/blueprints/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
from conditional import start_of_year, auth
from conditional.models.models import Conditional
from conditional.models.models import HouseMeeting
from conditional.models.models import MajorProject
from conditional.models.models import MemberHouseMeetingAttendance
from conditional.models.models import MemberSeminarAttendance
from conditional.models.models import TechnicalSeminar
from conditional.models.models import SpringEval
from conditional.util.auth import get_user
from conditional.util.flask import render_template
from conditional.util.housing import get_queue_position
from conditional.util.major_project import get_project_list
from conditional.util.member import gatekeep_values, get_active_members, get_freshman_data, get_voting_members, \
get_cm, get_hm, is_gatekeep_active, req_cm
from conditional.util.user_dict import user_dict_is_active, user_dict_is_bad_standing, user_dict_is_intromember, \
Expand Down Expand Up @@ -82,15 +82,23 @@ def display_dashboard(user_dict=None):

data['housing'] = housing

proj_list = get_project_list()

data['major_projects'] = [
{
'id': p.id,
'name': p.name,
'status': p.status,
'description': p.description
} for p in
MajorProject.query.filter(MajorProject.uid == uid,
MajorProject.date > start_of_year())]
"id": p.id,
"date": p.date,
"name": p.name,
"proj_name": p.name,
"tldr": p.tldr,
"time_spent": p.time_spent,
"skills": p.skills,
"desc": p.description,
"links": list(filter(None, p.links.split("\n"))),
"status": p.status,
}
for p in proj_list
]

data['major_projects_count'] = len(data['major_projects'])

Expand Down
148 changes: 119 additions & 29 deletions conditional/blueprints/major_project_submission.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,94 @@
import collections
import json
import requests
import os

from flask import Blueprint
from flask import request
from flask import jsonify
from flask import redirect

from sqlalchemy import desc

import requests
import boto3
import structlog

from conditional.util.context_processors import get_member_name
from werkzeug.utils import secure_filename

from conditional import db, get_user, auth, app
from conditional.models.models import MajorProject
from conditional.models.models import MajorProjectSkill

from conditional.util.ldap import ldap_is_eval_director
from conditional.util.context_processors import get_member_name
from conditional.util.ldap import ldap_get_member
from conditional.util.flask import render_template
from conditional.util.s3 import list_files_in_folder
from conditional.util.user_dict import user_dict_is_eval_director
from conditional.util.major_project import get_project_list

from conditional import db, start_of_year, get_user, auth, app
collections.Callable = collections.abc.Callable

logger = structlog.get_logger()

major_project_bp = Blueprint("major_project_bp", __name__)


@major_project_bp.route("/major_project/")
@auth.oidc_auth("default")
@get_user
def display_major_project(user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info("Display Major Project Page")

major_projects = [
# There is probably a better way to do this, but it does work
proj_list: list = get_project_list()

bucket: str = app.config['S3_BUCKET_ID']

major_projects: list[dict] = [
{
"id": p.id,
"date": p.date,
"username": p.uid,
"name": ldap_get_member(p.uid).cn,
"proj_name": p.name,
"tldr": p.tldr,
"time_spent": p.time_spent,
"skills": p.skills,
"desc": p.description,
"links": list(filter(None, p.links.split("\n"))),
"status": p.status,
"description": p.description,
"id": p.id,
"is_owner": bool(user_dict["username"] == p.uid),
"files": list_files_in_folder(bucket, f"{p.id}/")
}
for p in MajorProject.query.filter(
MajorProject.date > start_of_year()
).order_by(desc(MajorProject.id))
for p in proj_list
]

major_projects_len = len(major_projects)
# return names in 'first last (username)' format
return render_template(
"major_project_submission.html",
major_projects=major_projects,
major_projects_len=major_projects_len,
username=user_dict["username"],
)
major_projects_len=len(major_projects),
username=user_dict["username"])

@major_project_bp.route("/major_project/upload", methods=["POST"])
@auth.oidc_auth("default")
@get_user
def upload_major_project_files(user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info('Uploading Major Project File(s)')

if len(list(request.files.keys())) <1:
return "No file", 400

# Temporarily save files to a place, to be uploaded on submit
for _, file in request.files.lists():
file = file[0]
safe_name: str = secure_filename(file.filename)
filename = f"/tmp/{user_dict['username']}/{safe_name}"

os.makedirs(os.path.dirname(filename), exist_ok=True)
file.save(filename)

return jsonify({"success": True}), 200



@major_project_bp.route("/major_project/submit", methods=["POST"])
Expand All @@ -65,27 +99,79 @@
log.info("Submit Major Project")

post_data = request.get_json()

name = post_data["projectName"]
tldr = post_data['projectTldr']
time_spent = post_data['projectTimeSpent']
skills = post_data['projectSkills']
description = post_data["projectDescription"]
links = post_data['projectLinks']

user_id = user_dict['username']

log.info(user_id)

if name == "" or description == "":
# All fields are required in order to be able to submit the form
if not name or not tldr or not time_spent or not description:
return jsonify({"success": False}), 400
project = MajorProject(user_dict["username"], name, description)

# Don't you dare try pinging @channel
project: MajorProject = MajorProject(user_id, name, tldr, time_spent, description, links)

# Save the info to the database
db.session.add(project)
db.session.commit()

project = MajorProject.query.filter(
MajorProject.name == name,
MajorProject.uid == user_id
).first()

skills_list: list = list(filter(lambda x: x != 'None', skills))

for skill in skills_list:
skill = skill.strip()

if skill not in ("", 'None'):
mp_skill = MajorProjectSkill(project.id, skill)
db.session.add(mp_skill)

db.session.commit()

# Fail if attempting to retreive non-existent project
if project is None:
return jsonify({"success": False}), 500

# Sanitize input so that the Slackbot cannot ping @channel
name = name.replace("<!", "<! ")

username = user_dict["username"]
# Connect to S3 bucket
s3 = boto3.client("s3",
aws_access_key_id=app.config['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=app.config['AWS_SECRET_ACCESS_KEY'],
endpoint_url=app.config['S3_URI'])

# Collect all the locally cached files and put them in the bucket
temp_dir: str = f"/tmp/{user_id}"
if os.path.exists(temp_dir):
for file in os.listdir(temp_dir):
filepath = f"{temp_dir}/{file}"

s3.upload_file(filepath, 'major-project-media', f"{project.id}/{file}")

Check failure on line 159 in conditional/blueprints/major_project_submission.py

View check run for this annotation

CSH-Sonarqube-Community / SonarQube Code Analysis

conditional/blueprints/major_project_submission.py#L159

Add the 'ExpectedBucketOwner' to the 'ExtraArgs' parameter to verify S3 bucket ownership.

os.remove(filepath)

# Delete the temp directory once all the files have been stored in S3
os.rmdir(temp_dir)


# Send the slack ping only after we know that the data was properly saved to the DB
send_slack_ping(
{
"text": f"<!subteam^S5XENJJAH> *{get_member_name(username)}* ({username})"
f" submitted their major project, *{name}*! Please be sure to reach out"
f" to E-Board members to answer any questions they may have regarding"
f" your project!"
"text": f"<!subteam^S5XENJJAH> *{get_member_name(user_id)}* ({user_id})"
f" submitted their major project, *{name}*!"
}
)
db.session.add(project)
db.session.commit()

return jsonify({"success": True}), 200


Expand All @@ -95,7 +181,7 @@
def major_project_review(user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)

if not ldap_is_eval_director(user_dict["account"]):
if not user_dict_is_eval_director(user_dict["account"]):
return redirect("/dashboard", code=302)

post_data = request.get_json()
Expand All @@ -106,8 +192,10 @@

print(post_data)
MajorProject.query.filter(MajorProject.id == pid).update({"status": status})

db.session.flush()
db.session.commit()

return jsonify({"success": True}), 200


Expand All @@ -121,10 +209,12 @@
major_project = MajorProject.query.filter(MajorProject.id == pid).first()
creator = major_project.uid

if creator == user_dict["username"] or ldap_is_eval_director(user_dict["account"]):
if creator == user_dict["username"] or user_dict_is_eval_director(user_dict["account"]):
MajorProject.query.filter(MajorProject.id == pid).delete()

db.session.flush()
db.session.commit()

return jsonify({"success": True}), 200

return "Must be project owner to delete!", 401
Expand Down
22 changes: 18 additions & 4 deletions conditional/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,27 +127,41 @@ def __init__(self, fid, seminar_id):
self.fid = fid
self.seminar_id = seminar_id


class MajorProject(db.Model):
__tablename__ = 'major_projects'
id = Column(Integer, primary_key=True)
date = Column(Date, nullable=False)
uid = Column(String(32), nullable=False, index=True)
name = Column(String(64), nullable=False)
description = Column(Text)
tldr = Column(String(128), nullable=True)
time_spent = Column(Text, nullable=True)
description = Column(Text, nullable=False)
links = Column(Text, nullable=True)
active = Column(Boolean, nullable=False)
status = Column(Enum('Pending', 'Passed', 'Failed',
name="major_project_enum"),
nullable=False)

def __init__(self, uid, name, desc):
def __init__(self, uid, name, tldr, time_spent, description, links): # pylint: disable=too-many-positional-arguments
self.uid = uid
self.date = datetime.now()
self.name = name
self.description = desc
self.tldr = tldr
self.time_spent = time_spent
self.description = description
self.links = links
self.status = 'Pending'
self.active = True

class MajorProjectSkill(db.Model):
__tablename__ = "major_project_skills"
project_id = Column(Integer, ForeignKey('major_projects.id', ondelete="cascade"), nullable=False, primary_key=True)
skill = Column(Text, nullable=False, primary_key=True)

def __init__(self, project_id, skill):
self.project_id = project_id
self.skill = skill


class HouseMeeting(db.Model):
__tablename__ = 'house_meetings'
Expand Down
Loading