Skip to content
Merged
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
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,19 @@ This will run the asset pipeline, start the Python server, and start BrowserSync

To add new dependencies, add them to `requirements.in` and then run `pip-compile requirements.in` to produce a new locked `requirements.txt`. Do not edit `requirements.txt` directly as it will be overwritten by future PRs.

after app initialization
### Local database

You can run the database locally using the docker compose, make sure to upgrade it as explained below

To populate it with dev data for example, you can use the command

```
PGPASSWORD='[DB PASSWORD]' pg_dump -h postgres.csh.rit.edu -p 5432 -U conditional-dev conditional-dev | PGPASSWORD='fancypantspassword' psql -h localhost -p 5432 -U conditional conditional
```

This can be helpful for changing the database schema

NOTE: to use flask db commands with a database running in the compose file, you will have to update your url to point to localhost, not conditional-postgres

### Database Migrations

Expand Down
11 changes: 9 additions & 2 deletions conditional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse
# pylint: disable=wrong-import-order
from conditional.util import context_processors
from conditional.util.auth import get_user
from conditional.util.member import gatekeep_status
from conditional.util.member import gatekeep_status, get_voting_members
from .blueprints.dashboard import dashboard_bp # pylint: disable=ungrouped-imports
from .blueprints.attendance import attendance_bp
from .blueprints.major_project_submission import major_project_bp
Expand Down Expand Up @@ -167,7 +167,7 @@ def health():


@app.route("/gatekeep/<username>")
def gatekeep(username):
def gatekeep_user(username):
token = request.headers.get("X-VOTE-TOKEN", "")
if token != app.config["VOTE_TOKEN"]:
return "Users cannot access this page", 403
Expand All @@ -178,6 +178,13 @@ def gatekeep(username):

return gatekeep_data, 200

@app.route("/gatekeep")
def gatekeep_all():
token = request.headers.get("X-VOTE-TOKEN", "")
if token != app.config["VOTE_TOKEN"]:
return "Users cannot access this page", 403
return list(get_voting_members()), 200


@app.errorhandler(404)
@app.errorhandler(500)
Expand Down
9 changes: 5 additions & 4 deletions conditional/blueprints/member_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,22 +462,20 @@ def member_management_upgrade_user(user_dict=None):
db.session.add(new_acct)
for fca in FreshmanCommitteeAttendance.query.filter(FreshmanCommitteeAttendance.fid == fid):
db.session.add(MemberCommitteeAttendance(uid, fca.meeting_id))
db.session.delete(fca)

for fts in FreshmanSeminarAttendance.query.filter(FreshmanSeminarAttendance.fid == fid):
db.session.add(MemberSeminarAttendance(uid, fts.seminar_id))
db.session.delete(fts)

for fhm in FreshmanHouseMeetingAttendance.query.filter(FreshmanHouseMeetingAttendance.fid == fid):
# Don't duplicate HM attendance records
mhm = MemberHouseMeetingAttendance.query.filter(
MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id).first()
MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id,
MemberHouseMeetingAttendance.uid == uid).first()
if mhm is None:
db.session.add(MemberHouseMeetingAttendance(
uid, fhm.meeting_id, fhm.excuse, fhm.attendance_status))
else:
log.info(f'Duplicate house meeting attendance! fid: {fid}, uid: {uid}, id: {fhm.meeting_id}')
db.session.delete(fhm)

new_account = ldap_get_member(uid)
if acct.onfloor_status:
Expand All @@ -487,6 +485,9 @@ def member_management_upgrade_user(user_dict=None):
if acct.room_number:
ldap_set_roomnumber(new_account, acct.room_number)

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

db.session.delete(acct)

db.session.flush()
Expand Down
6 changes: 3 additions & 3 deletions conditional/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def __init__(self, uid, meeting_id):
class FreshmanCommitteeAttendance(db.Model):
__tablename__ = 'freshman_committee_attendance'
id = Column(Integer, primary_key=True)
fid = Column(ForeignKey('freshman_accounts.id'), nullable=False)
fid = Column(ForeignKey('freshman_accounts.id', ondelete="cascade"), nullable=False)
meeting_id = Column(ForeignKey('committee_meetings.id'), nullable=False)

def __init__(self, fid, meeting_id):
Expand Down Expand Up @@ -120,7 +120,7 @@ def __init__(self, uid, seminar_id):
class FreshmanSeminarAttendance(db.Model):
__tablename__ = 'freshman_seminar_attendance'
id = Column(Integer, primary_key=True)
fid = Column(ForeignKey('freshman_accounts.id'), nullable=False)
fid = Column(ForeignKey('freshman_accounts.id', ondelete="cascade"), nullable=False)
seminar_id = Column(ForeignKey('technical_seminars.id'), nullable=False)

def __init__(self, fid, seminar_id):
Expand Down Expand Up @@ -178,7 +178,7 @@ def __init__(self, uid, meeting_id, excuse, status):
class FreshmanHouseMeetingAttendance(db.Model):
__tablename__ = 'freshman_hm_attendance'
id = Column(Integer, primary_key=True)
fid = Column(ForeignKey('freshman_accounts.id'), nullable=False)
fid = Column(ForeignKey('freshman_accounts.id', ondelete="cascade"), nullable=False)
meeting_id = Column(ForeignKey('house_meetings.id'), nullable=False)
excuse = Column(Text)
attendance_status = Column(attendance_enum)
Expand Down
109 changes: 65 additions & 44 deletions conditional/util/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from sqlalchemy import func, or_

from conditional import start_of_year
from conditional.models.models import CommitteeMeeting
from conditional.models.models import CommitteeMeeting, FreshmanAccount
from conditional.models.models import CurrentCoops
from conditional.models.models import FreshmanEvalData
from conditional.models.models import HouseMeeting
Expand All @@ -21,6 +21,7 @@
from conditional.util.ldap import ldap_is_intromember
from conditional.util.ldap import ldap_get_member


@service_cache(maxsize=1024)
def get_members_info():
members = ldap_get_current_students()
Expand Down Expand Up @@ -57,18 +58,18 @@ def get_freshman_data(user_name):
MemberCommitteeAttendance.query.filter(
MemberCommitteeAttendance.uid == user_name
) if CommitteeMeeting.query.filter(
CommitteeMeeting.id == m.meeting_id).first().approved]
CommitteeMeeting.id == m.meeting_id).first().approved]
freshman['committee_meetings'] = len(c_meetings)
# technical seminar total
t_seminars = [s.seminar_id for s in
MemberSeminarAttendance.query.filter(
MemberSeminarAttendance.uid == user_name
) if TechnicalSeminar.query.filter(
TechnicalSeminar.id == s.seminar_id).first().approved]
TechnicalSeminar.id == s.seminar_id).first().approved]
freshman['ts_total'] = len(t_seminars)
attendance = [m.name for m in TechnicalSeminar.query.filter(
TechnicalSeminar.id.in_(t_seminars)
)]
)]

freshman['ts_list'] = attendance

Expand Down Expand Up @@ -114,20 +115,20 @@ def get_cm(member):

def get_hm(member, only_absent=False):
h_meetings = MemberHouseMeetingAttendance.query.outerjoin(
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id).with_entities(
MemberHouseMeetingAttendance.meeting_id,
MemberHouseMeetingAttendance.attendance_status,
HouseMeeting.date).filter(
HouseMeeting.date > start_of_year(),
MemberHouseMeetingAttendance.uid == member.uid)
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id).with_entities(
MemberHouseMeetingAttendance.meeting_id,
MemberHouseMeetingAttendance.attendance_status,
HouseMeeting.date).filter(
HouseMeeting.date > start_of_year(),
MemberHouseMeetingAttendance.uid == member.uid)
if only_absent:
h_meetings = h_meetings.filter(MemberHouseMeetingAttendance.attendance_status == "Absent")
return h_meetings


# @service_cache(maxsize=128)
def req_cm(uid, members_on_coop = None):
def req_cm(uid, members_on_coop=None):
# Get the number of required committee meetings based on if the member
# is going on co-op in the current operating session.
on_coop = False
Expand All @@ -143,14 +144,16 @@ def req_cm(uid, members_on_coop = None):
return 15
return 30


@service_cache(maxsize=256)
def get_voting_members():
if datetime.today() < datetime(start_of_year().year, 12, 31):
today = datetime.today()
if today < datetime(start_of_year().year, 12, 31):
semester = "Fall"
semester_start = datetime(start_of_year().year,6,1)
semester_start = datetime(start_of_year().year, 6, 1)
else:
semester = "Spring"
semester_start = datetime(start_of_year().year + 1,1,1)
semester_start = datetime(start_of_year().year + 1, 1, 1)

active_members = set(ldap_get_active_members())
intro_members = set(ldap_get_intro_members())
Expand All @@ -169,8 +172,8 @@ def get_voting_members():
coop_members = set(coop_members)

passed_fall_members = FreshmanEvalData.query.filter(
FreshmanEvalData.freshman_eval_result == "Passed",
FreshmanEvalData.eval_date > start_of_year(),
FreshmanEvalData.freshman_eval_result == "Passed",
FreshmanEvalData.eval_date > start_of_year(),
).with_entities(
func.array_agg(FreshmanEvalData.uid)
).scalar()
Expand All @@ -183,7 +186,13 @@ def get_voting_members():
active_not_intro = active_members - intro_members
active_not_intro = set(map(lambda member: member.uid, active_not_intro))

elligible_members = (active_not_intro - coop_members) | passed_fall_members
eligible_members = (active_not_intro - coop_members) | passed_fall_members

# Check to see if there's an Intro Evals in the future of this semester. If there is, everyone gets to vote!
before_evals_one = len(FreshmanAccount.query.filter(FreshmanAccount.eval_date > today).limit(1).all())
before_evals_two = len(FreshmanEvalData.query.filter(FreshmanEvalData.eval_date > today).limit(1).all())
if before_evals_one > 0 or before_evals_two > 0:
return eligible_members

passing_dm = set(member.uid for member in MemberCommitteeAttendance.query.join(
CommitteeMeeting,
Expand All @@ -200,7 +209,7 @@ def get_voting_members():
).group_by(
MemberCommitteeAttendance.uid
).having(
func.count(MemberCommitteeAttendance.uid) >= 6 #pylint: disable=not-callable
func.count(MemberCommitteeAttendance.uid) >= 6 # pylint: disable=not-callable
).with_entities(
MemberCommitteeAttendance.uid
).all())
Expand All @@ -216,58 +225,69 @@ def get_voting_members():
).group_by(
MemberSeminarAttendance.uid
).having(
func.count(MemberSeminarAttendance.uid) >= 2 #pylint: disable=not-callable
func.count(MemberSeminarAttendance.uid) >= 2 # pylint: disable=not-callable
).all())

passing_hm = set(member.uid for member in MemberHouseMeetingAttendance.query.join(
absent_hm = set(member.uid for member in MemberHouseMeetingAttendance.query.join(
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
).filter(
HouseMeeting.date >= semester_start, or_(
MemberHouseMeetingAttendance.attendance_status == 'Attended',
# MemberHouseMeetingAttendance.attendance_status == 'Excused'
MemberHouseMeetingAttendance.attendance_status == 'Absent',
)
).with_entities(
MemberHouseMeetingAttendance.uid
).group_by(
MemberHouseMeetingAttendance.uid
).having(
func.count(MemberHouseMeetingAttendance.uid) >= 6 #pylint: disable=not-callable
func.count(MemberHouseMeetingAttendance.uid) > 1 # pylint: disable=not-callable
).all())

passing_reqs = passing_dm & passing_ts & passing_hm
passing_reqs = (passing_dm & passing_ts) - absent_hm

return eligible_members & passing_reqs

return elligible_members & passing_reqs

def gatekeep_status(username):
if datetime.today() < datetime(start_of_year().year, 12, 31):
today = datetime.today()
# Check to see if there's an Intro Evals in the future of this semester. If there is, everyone gets to vote!
before_evals_one = len(FreshmanAccount.query.filter(FreshmanAccount.eval_date > today).limit(1).all())
before_evals_two = len(FreshmanEvalData.query.filter(FreshmanEvalData.eval_date > today).limit(1).all())
if before_evals_one > 0 or before_evals_two > 0:
return {
"result": True,
"h_meetings_missed": 0,
"c_meetings": 0,
"t_seminars": 0,
}
if today < datetime(start_of_year().year, 12, 31):
semester = "Fall"
semester_start = datetime(start_of_year().year,6,1)
semester_start = datetime(start_of_year().year, 6, 1)
else:
semester = "Spring"
semester_start = datetime(start_of_year().year + 1,1,1)
semester_start = datetime(start_of_year().year + 1, 1, 1)

# groups
ldap_member = ldap_get_member(username)
is_intro_member = ldap_is_intromember(ldap_member)
is_active_member = ldap_is_active(ldap_member) and not is_intro_member

is_on_coop = (
CurrentCoops.query.filter(
CurrentCoops.date_created > start_of_year(),
CurrentCoops.semester == semester,
CurrentCoops.uid == username,
).first()
is not None
CurrentCoops.query.filter(
CurrentCoops.date_created > start_of_year(),
CurrentCoops.semester == semester,
CurrentCoops.uid == username,
).first()
is not None
)

passed_fall = (
FreshmanEvalData.query.filter(
FreshmanEvalData.freshman_eval_result == "Passed",
FreshmanEvalData.eval_date > start_of_year(),
FreshmanEvalData.uid == username,
).first()
is not None
FreshmanEvalData.query.filter(
FreshmanEvalData.freshman_eval_result == "Passed",
FreshmanEvalData.eval_date > start_of_year(),
FreshmanEvalData.uid == username,
).first()
is not None
)
eligibility_of_groups = (is_active_member and not is_on_coop) or passed_fall

Expand Down Expand Up @@ -298,22 +318,23 @@ def gatekeep_status(username):
.count()
)
# number of house meetings attended in the current semester
h_meetings = (
h_meetings_missed = (
MemberHouseMeetingAttendance.query.join(
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id,
)
.filter(
MemberHouseMeetingAttendance.attendance_status == 'Absent',
MemberHouseMeetingAttendance.uid == username,
HouseMeeting.date >= semester_start
)
.count()
)
result = eligibility_of_groups and (d_meetings >= 6 and t_seminars >= 2 and h_meetings >= 6)
result = eligibility_of_groups and (d_meetings >= 6 and t_seminars >= 2 and h_meetings_missed < 2) # pylint: disable=chained-comparison

return {
"result": result,
"h_meetings": h_meetings,
"h_meetings_missed": h_meetings_missed,
"c_meetings": d_meetings,
"t_seminars": t_seminars,
}
2 changes: 1 addition & 1 deletion config.env.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
PROFILING = env.get("CONDITIONAL_PROFILING", "false").lower() == "true"

# DB Info
SQLALCHEMY_DATABASE_URI = env.get("SQLALCHEMY_DATABASE_URI", "")
SQLALCHEMY_DATABASE_URI = "postgresql://conditional:fancypantspassword@conditional-postgres:5432/conditional"
SQLALCHEMY_TRACK_MODIFICATIONS = False

# LDAP config
Expand Down
24 changes: 24 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: "3"
services:
conditional:
build: .
container_name: conditional
depends_on:
- conditional-postgres
ports:
- "127.0.0.1:8080:8080"
conditional-postgres:
image: docker.io/postgres
container_name: conditional-postgres
environment:
POSTGRES_PASSWORD: fancypantspassword
POSTGRES_USER: conditional
POSTGRES_DATABASE: conditional
ports:
- "127.0.0.1:5432:5432"
volumes:
- pgdata:/var/lib/postgresql

volumes:
pgdata:

Loading