Skip to content

Commit 8c4d510

Browse files
authored
Merge pull request #10 from JoelEager/master
Packet season creation CLI
2 parents ffb468f + 7f32bb8 commit 8c4d510

File tree

9 files changed

+143
-29
lines changed

9 files changed

+143
-29
lines changed

config.env.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
PORT = environ.get("PACKET_PORT", "8000")
77
SERVER_NAME = environ.get("PACKET_SERVER_NAME", IP + ":" + PORT)
88
SECRET_KEY = environ.get("PACKET_SECRET_KEY", "PLEASE_REPLACE_ME")
9-
REALM = environ.get("PACKET_REALM", "intro")
109

1110
# OpenID Connect SSO config
11+
REALM = environ.get("PACKET_REALM", "intro")
12+
1213
OIDC_ISSUER = environ.get("PACKET_OIDC_ISSUER", "https://sso.csh.rit.edu/auth/realms/csh")
1314
OIDC_CLIENT_ID = environ.get("PACKET_OIDC_CLIENT_ID", "packet")
1415
OIDC_CLIENT_SECRET = environ.get("PACKET_OIDC_CLIENT_SECRET", "PLEASE_REPLACE_ME")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""General schema cleanup and improvements
2+
3+
Revision ID: 0eeabc7d8f74
4+
Revises: b1c013f236ab
5+
Create Date: 2018-08-31 18:07:19.767140
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '0eeabc7d8f74'
14+
down_revision = 'b1c013f236ab'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
op.alter_column('signature_fresh', 'freshman', new_column_name='freshman_username')
21+
22+
23+
def downgrade():
24+
op.alter_column('signature_fresh', 'freshman_username', new_column_name='freshman')

packet/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from .routes import upperclassmen
4040
else:
4141
from .routes import freshmen
42-
from .routes import api, shared
4342

43+
from .routes import api, shared
4444
from . import commands
4545
from . import models

packet/commands.py

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
"""
44

55
from secrets import token_hex
6+
from datetime import datetime, time, timedelta
7+
import csv
8+
import click
69

710
from . import app, db
8-
from .models import Freshman, Packet, UpperSignature
11+
from .models import Freshman, Packet, UpperSignature, FreshSignature
12+
from .ldap import ldap_get_eboard, ldap_get_live_onfloor
913

1014
@app.cli.command("create-secret")
1115
def create_secret():
@@ -15,22 +19,108 @@ def create_secret():
1519
print("Here's your random secure token:")
1620
print(token_hex())
1721

18-
@app.cli.command("create-packet")
19-
def create_packet():
22+
class CSVFreshman:
23+
def __init__(self, row):
24+
self.name = row[0]
25+
self.rit_username = row[3]
26+
self.onfloor = row[1] == "TRUE"
27+
28+
def parse_csv(freshmen_csv):
29+
print("Parsing file...")
30+
try:
31+
with open(freshmen_csv, newline='') as freshmen_csv_file:
32+
return {freshman.rit_username: freshman for freshman in map(CSVFreshman, csv.reader(freshmen_csv_file))}
33+
except Exception as e:
34+
print("Failure while parsing CSV")
35+
raise e
36+
37+
@app.cli.command("sync-freshmen")
38+
@click.argument("freshmen_csv")
39+
def sync_freshmen(freshmen_csv):
2040
"""
21-
Example/test code for adding a new packet to the database.
41+
Updates the freshmen entries in the DB to match the given CSV.
2242
"""
23-
print("Generating new rows...")
43+
freshmen_in_csv = parse_csv(freshmen_csv)
44+
45+
print("Syncing contents with the DB...")
46+
freshmen_in_db = {freshman.rit_username: freshman for freshman in Freshman.query.all()}
2447

25-
freshman = Freshman(rit_username="bob1234", name="Bob Freshy", onfloor=True)
26-
db.session.add(freshman)
48+
for csv_freshman in freshmen_in_csv.values():
49+
if csv_freshman.rit_username not in freshmen_in_db:
50+
# This is a new freshman so add them to the DB
51+
freshmen_in_db[csv_freshman.rit_username] = Freshman(rit_username=csv_freshman.rit_username,
52+
name=csv_freshman.name, onfloor=csv_freshman.onfloor)
53+
db.session.add(freshmen_in_db[csv_freshman.rit_username])
54+
else:
55+
# This freshman is already in the DB so just update them
56+
freshmen_in_db[csv_freshman.rit_username].onfloor = csv_freshman.onfloor
57+
freshmen_in_db[csv_freshman.rit_username].name = csv_freshman.name
2758

28-
packet = Packet(freshman=freshman)
29-
db.session.add(packet)
59+
# Update all freshmen entries that represent people who are no longer freshmen
60+
for freshman in filter(lambda freshman: freshman.rit_username not in freshmen_in_csv, freshmen_in_db.values()):
61+
freshman.onfloor = False
3062

31-
db.session.add(UpperSignature(packet=packet, member="somehuman"))
32-
db.session.add(UpperSignature(packet=packet, member="reeehuman", eboard=True))
63+
# Update the freshmen signatures of each open or future packet
64+
for packet in Packet.query.filter(Packet.end > datetime.now()).all():
65+
# Handle the freshmen that are no longer onfloor
66+
for fresh_sig in filter(lambda fresh_sig: not fresh_sig.freshman.onfloor, packet.fresh_signatures):
67+
FreshSignature.query.filter_by(packet_id=fresh_sig.packet_id,
68+
freshman_username=fresh_sig.freshman_username).delete()
69+
70+
# Add any new onfloor freshmen
71+
# pylint: disable=cell-var-from-loop
72+
current_fresh_sigs = set(map(lambda fresh_sig: fresh_sig.freshman_username, packet.fresh_signatures))
73+
for csv_freshman in filter(lambda csv_freshman: csv_freshman.rit_username not in current_fresh_sigs,
74+
freshmen_in_csv.values()):
75+
db.session.add(FreshSignature(packet=packet, freshman=freshmen_in_db[csv_freshman.rit_username]))
3376

3477
db.session.commit()
78+
print("Done!")
79+
80+
@app.cli.command("create-packets")
81+
@click.argument("freshmen_csv")
82+
def create_packets(freshmen_csv):
83+
"""
84+
Creates a new packet season for each of the freshmen in the given CSV.
85+
"""
86+
print("WARNING: The 'sync-freshmen' command must be run first to ensure that the state of floor is up to date.")
87+
if input("Continue? (y/N): ").lower() != "y":
88+
return
89+
90+
# Collect the necessary data
91+
base_date = None
92+
while base_date is None:
93+
try:
94+
date_str = input("Input the first day of packet season (format: MM/DD/YYYY): ")
95+
base_date = datetime.strptime(date_str, "%m/%d/%Y").date()
96+
except ValueError:
97+
pass
3598

99+
start = datetime.combine(base_date, time(hour=19))
100+
end = datetime.combine(base_date, time(hour=23, minute=59)) + timedelta(days=14)
101+
102+
print("Fetching data from LDAP...")
103+
eboard = [member.uid for member in ldap_get_eboard()]
104+
onfloor = [member.uid for member in ldap_get_live_onfloor()]
105+
106+
freshmen_in_csv = parse_csv(freshmen_csv)
107+
108+
# Create the new packets and the signatures
109+
print("Creating DB entries...")
110+
for freshman in freshmen_in_csv.values():
111+
packet = Packet(freshman=Freshman.query.filter_by(rit_username=freshman.rit_username).first(), start=start,
112+
end=end)
113+
db.session.add(packet)
114+
115+
for username in eboard:
116+
db.session.add(UpperSignature(packet=packet, member=username, eboard=True))
117+
118+
for username in onfloor:
119+
db.session.add(UpperSignature(packet=packet, member=username))
120+
121+
for onfloor_freshman in Freshman.query.filter_by(onfloor=True).filter(Freshman.rit_username !=
122+
freshman.rit_username).all():
123+
db.session.add(FreshSignature(packet=packet, freshman=onfloor_freshman))
124+
125+
db.session.commit()
36126
print("Done!")

packet/ldap.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,15 @@ def ldap_get_eboard():
8585
) + _ldap_get_group_members("eboard-financial") + _ldap_get_group_members("eboard-history"
8686
) + _ldap_get_group_members("eboard-imps") + _ldap_get_group_members("eboard-opcomm"
8787
) + _ldap_get_group_members("eboard-research") + _ldap_get_group_members("eboard-social"
88-
) + _ldap_get_group_members("eboard-secretary") + _ldap_get_group_members("eboard-pr")
88+
) + _ldap_get_group_members("eboard-pr")
8989

9090
return members
9191

9292

9393
def ldap_get_live_onfloor():
94+
"""
95+
:return: All upperclassmen who live on floor and are not eboard
96+
"""
9497
members = []
9598
onfloor = ldap_get_onfloor_members()
9699
for member in onfloor:

packet/models.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Defines the application's database models.
33
"""
44

5-
from datetime import datetime, timedelta
5+
from datetime import datetime
66
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Boolean
77
from sqlalchemy.orm import relationship
88

@@ -12,15 +12,12 @@
1212
REQUIRED_MISC_SIGNATURES = 15
1313

1414

15-
def end_date():
16-
return datetime.now() + timedelta(days=14)
17-
18-
1915
class Freshman(db.Model):
2016
__tablename__ = "freshman"
2117
rit_username = Column(String(10), primary_key=True)
2218
name = Column(String(64), nullable=False)
2319
onfloor = Column(Boolean, nullable=False)
20+
fresh_signatures = relationship("FreshSignature")
2421

2522
# One freshman can have multiple packets if they repeat the intro process
2623
packets = relationship("Packet", order_by="desc(Packet.id)")
@@ -36,10 +33,10 @@ class Packet(db.Model):
3633
__tablename__ = "packet"
3734
id = Column(Integer, primary_key=True, autoincrement=True)
3835
freshman_username = Column(ForeignKey("freshman.rit_username"))
39-
start = Column(DateTime, default=datetime.now, nullable=False)
40-
end = Column(DateTime, default=end_date, nullable=False)
41-
info_eboard = Column(Text, nullable=True) # Used to fulfil the eboard description requirement
42-
info_events = Column(Text, nullable=True) # Used to fulfil the events list requirement
36+
start = Column(DateTime, nullable=False)
37+
end = Column(DateTime, nullable=False)
38+
info_eboard = Column(Text, nullable=True) # Used to fulfil the eboard description requirement
39+
info_events = Column(Text, nullable=True) # Used to fulfil the events list requirement
4340
info_achieve = Column(Text, nullable=True) # Used to fulfil the technical achievements list requirement
4441

4542
freshman = relationship("Freshman", back_populates="packets")
@@ -94,6 +91,7 @@ class FreshSignature(db.Model):
9491
updated = Column(DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
9592

9693
packet = relationship("Packet", back_populates="fresh_signatures")
94+
freshman = relationship("Freshman", back_populates="fresh_signatures")
9795

9896

9997
class MiscSignature(db.Model):

packet/utils.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Credit to Liam Middlebrook and Ram Zallan
22
# https://github.com/liam-middlebrook/gallery
3-
import datetime
43
from functools import wraps
54

65
import requests
@@ -25,8 +24,7 @@ def wrapped_function(*args, **kwargs):
2524
if session["id_token"]["iss"] == INTRO_REALM:
2625
info = {
2726
"realm": "intro",
28-
"uid": uid,
29-
"current_year": parse_account_year(str(datetime.datetime.now().strftime("%Y%m")))
27+
"uid": uid
3028
}
3129
else:
3230
uuid = str(session["userinfo"].get("sub", ""))
@@ -37,8 +35,7 @@ def wrapped_function(*args, **kwargs):
3735
"uid": uid,
3836
"user_obj": user_obj,
3937
"member_info": get_member_info(uid),
40-
"color": requests.get('https://themeswitcher.csh.rit.edu/api/colour').content,
41-
"current_year": parse_account_year(str(datetime.datetime.now().strftime("%Y%m")))
38+
"color": requests.get('https://themeswitcher.csh.rit.edu/api/colour').content
4239
}
4340

4441
kwargs["info"] = info

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Flask==1.0.2
22
Flask-pyoidc>=1.3.0
33
flask_sqlalchemy==2.3.2
4-
psycopg2==2.7.5
4+
psycopg2-binary==2.7.5
55
Flask-Migrate==2.2.1
66
pylint==2.1.1
77
flask_saml==0.4.3

wsgi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from app import app

0 commit comments

Comments
 (0)