Skip to content

Commit

Permalink
Merge branch 'finals-database-support' into semester-delete
Browse files Browse the repository at this point in the history
  • Loading branch information
Tevetron authored Nov 5, 2024
2 parents d800f46 + f2975a6 commit 72a60af
Show file tree
Hide file tree
Showing 9 changed files with 660 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ courses20.xml
compose-dev.yaml
rpi_data/get-summer-2023-2.sh
rpi_data/summer-20232.csv
.venv/
6 changes: 3 additions & 3 deletions docker-compose.development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ services:
- ./src/web:/app
- web_node_modules:/app/node_modules/
environment:
- YACS_API_HOST=http://yacs_api:5000
- YACS_API_HOST=http://yacs_api:4000

yacs_api:
command: /bin/bash -c "python tables/database_session.py && PYTHONPATH=. alembic upgrade head && uvicorn app:app --reload --host 0.0.0.0 --port 5000"
command: /bin/bash -c "python tables/database_session.py && PYTHONPATH=. alembic upgrade head && uvicorn app:app --reload --host 0.0.0.0 --port 4000"
ports:
- 5000:5000
- 4000:4000
volumes:
- ./src/api:/usr/src
environment:
Expand Down
24 changes: 24 additions & 0 deletions rpi_data/modules/post_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import requests
import base64
import os

url = os.environ.get('yacs_url')
url = "http://localhost:5000"
api_location = url + "/api/bulkCourseUpload"
__location__ = os.path.realpath(os.path.join(
os.getcwd(), os.path.dirname(__file__)))


def csvUpload(fileName):
endpath = os.path.join(__location__, fileName)
endpath = os.path.dirname(os.path.dirname(endpath)) + "\\" + fileName
print(endpath)
uploadFile = {'file': open(endpath, 'rb')}
data = {'isPubliclyVisible': 'on'}

r = requests.post(api_location, files=uploadFile, data=data)
print(r.reason, r.status_code)


if __name__ == "__main__":
csvUpload("spring-2022.csv")
431 changes: 431 additions & 0 deletions rpi_data/out.csv

Large diffs are not rendered by default.

39 changes: 36 additions & 3 deletions src/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
import db.connection as connection
import db.classinfo as ClassInfo
import db.courses as Courses
import db.finals as Finals
import db.professor as All_professors
import db.semester_info as SemesterInfo
import db.semester_date_mapping as DateMapping
import db.admin as AdminInfo
import pandas as pd
import db.student_course_selection as CourseSelect
import db.user as UserModel
import controller.user as user_controller
import controller.session as session_controller
import controller.userevent as event_controller
from io import StringIO
from sqlalchemy.orm import Session
import json
import os
import pandas as pd
from constants import Constants

# NOTE: on caching
Expand All @@ -41,6 +41,7 @@
db_conn = connection.db
class_info = ClassInfo.ClassInfo(db_conn)
courses = Courses.Courses(db_conn, FastAPICache)
finals = Finals.Finals(db_conn, FastAPICache)
date_range_map = DateMapping.semester_date_mapping(db_conn)
admin_info = AdminInfo.Admin(db_conn)
course_select = CourseSelect.student_course_selection(db_conn)
Expand Down Expand Up @@ -178,7 +179,6 @@ async def uploadHandler(

@app.post('/api/bulkProfessorUpload')
async def uploadJSON(
isPubliclyVisible: str = Form(...),
file: UploadFile = File(...)):
# Check to make sure the user has sent a file
if not file:
Expand Down Expand Up @@ -214,6 +214,39 @@ async def remove_semester(semester_id: str):
semester, error = semester_info.delete_semester(semester=semester_id)
return Response(status_code=200) if not error else Response(str(error), status_code=500)

@app.post('/api/final')
async def uploadHandler(
file: UploadFile = File(...)):
# check for user files
print("in process")
if not file:
return Response("No file received", 400)
if file.filename.find('.') == -1 or file.filename.rsplit('.', 1)[1].lower() != 'csv':
return Response("File must have csv extension", 400)
# get file
contents = await file.read()
csv_file = StringIO(contents.decode())
# Populate DB from CSV
error = finals.populate_from_csv(csv_file)
return Response(error.__str__(), status_code=500) if error else Response("Upload Successful", status_code=200)

@app.get('/api/final/{semester}')
@cache(expire=Constants.DAY_IN_SECONDS, coder=PickleCoder, namespace="API_CACHE")
async def getHandler(semester: str):
if not semester:
return Response("No semester received", 400)
print(semester)
final, error = finals.get_by_semester(semester)
return final if not error else Response(error, status_code=500)

@app.delete('/api/final/{semester}')
async def deleteHandler(semester: str):
if not semester:
return Response("No semester received", 400)
print(semester)
_, error = finals.delete_by_semester(semester)
return Response(error.__str__(), status_code=500) if error else Response("Delete Successful", status_code=200)

@app.post('/api/mapDateRangeToSemesterPart')
async def map_date_range_to_semester_part_handler(request: Request):
# This depends on date_start, date_end, and semester_part_name being
Expand Down
110 changes: 110 additions & 0 deletions src/api/db/finals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import csv
import re
from psycopg2.extras import RealDictCursor
from ast import literal_eval
import asyncio

# https://stackoverflow.com/questions/54839933/importerror-with-from-import-x-on-simple-python-files
if __name__ == "__main__":
import connection
else:
from . import connection


class Finals:
def __init__(self, db_wrapper, cache):
self.db = db_wrapper
self.cache = cache

def populate_from_csv(self, csv_text):
conn = self.db.get_connection()
reader = csv.DictReader(csv_text)
# for each course entry insert sections and course sessions
with conn.cursor(cursor_factory=RealDictCursor) as transaction:
for row in reader:
try:
# finals
transaction.execute(
"""
INSERT INTO
final(
semester,
course,
section,
"start",
"end",
room_assignment
)
VALUES (
%(Semester)s,
%(Course)s,
%(Section)s,
TO_TIMESTAMP(%(Start)s, 'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP(%(End)s, 'YYYY-MM-DD HH24:MI:SS'),
%(Room_Assignment)s
)
ON CONFLICT (semester, course, section, start, room_assignment) DO NOTHING;
""",
{
"Semester": row['Season'] + ' ' + row['Year'],
"Course": row['Major'] + '-' + row['Course'],
"Section": "1" if row['Section'] == '' else row['Section'],
"Start": row['Start'],
"End": row['End'],
"Room_Assignment": row['Building'] + '-' + row['Room_Number']
}
)
except Exception as e:
print(e)
conn.rollback()
return e
conn.commit()
self.clear_cache()
return None

def get_by_semester(self, semester):
return self.db.execute("""
SELECT * FROM final
WHERE semester=%(Semester)s
ORDER BY start ASC;
""", {
"Semester": semester
}, isSELECT=True)

def delete_by_semester(self, semester):
self.clear_cache()
return self.db.execute("""
BEGIN TRANSACTION;
DELETE FROM final
WHERE semester=%(Semester)s;
COMMIT;
""", {
"Semester": semester
}, isSELECT=False)

def bulk_delete(self, semesters):
for semester in semesters:
_, error = self.delete_by_semester(semester)
if error:
print(error)
return error
self.clear_cache()
return None

def clear_cache(self):
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None

if loop and loop.is_running():
loop.create_task(self.cache.clear(namespace="API_CACHE"))
else:
asyncio.run(self.cache.clear("API_CACHE"))

if __name__ == "__main__":
# os.chdir(os.path.abspath("../rpi_data"))
# fileNames = glob.glob("*.csv")
csv_text = open('../../../rpi_data/out.csv', 'r')
finals = Finals(connection.db)
finals.populate_from_csv(csv_text)
36 changes: 36 additions & 0 deletions src/api/migrations/versions/2024-10-15_adding_finals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""adding finals
Revision ID: 1ff4f05483ad
Revises: 6c9a1ffc58a5
Create Date: 2024-10-15 21:38:59.576120
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '6c9a1ffc58a5'
down_revision = 'c959c263997f'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('final',
sa.Column('semester', sa.VARCHAR(length=255), nullable=False),
sa.Column('course', sa.VARCHAR(length=255), nullable=False),
sa.Column('section', sa.INTEGER(), nullable=False),
sa.Column('start', postgresql.TIMESTAMP(), nullable=False),
sa.Column('end', postgresql.TIMESTAMP(), nullable=False),
sa.Column('room_assignment', sa.VARCHAR(length=255), nullable=False),
sa.PrimaryKeyConstraint('semester', 'course', 'section', 'start', 'room_assignment')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('final')
# ### end Alembic commands ###
1 change: 1 addition & 0 deletions src/api/tables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .course_session import CourseSession
from .course import Course
from .event import Event
from .final import Final
from .semester_date_range import SemesterDateRange
from .semester_info import SemesterInfo
from .student_course_selection import StudentCourseSelection
Expand Down
18 changes: 18 additions & 0 deletions src/api/tables/final.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from sqlalchemy import Column, PrimaryKeyConstraint
from sqlalchemy.dialects.postgresql import INTEGER, VARCHAR, TIME

from .database import Base

class Final(Base):
__tablename__ = "final"

semester = Column(VARCHAR(length=255))
course = Column(VARCHAR(length=255))
section = Column(INTEGER)
start = Column(TIME)
end = Column(TIME)
room_assignment = Column(VARCHAR(length=255))

__table_args__ = (
PrimaryKeyConstraint('semester', 'course', 'section', 'start', 'room_assignment'),
)

0 comments on commit 72a60af

Please sign in to comment.