From b05bdd621ddace0d65c4914aea5603cbc3a08da9 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 13:49:30 -0500 Subject: [PATCH 1/9] added gatekeep page as currently implemented in constitution --- conditional/blueprints/dashboard.py | 17 ++++++- conditional/templates/dashboard.html | 72 +++++++++++++++++++++++++++- conditional/util/member.py | 44 ++++++++++------- 3 files changed, 113 insertions(+), 20 deletions(-) diff --git a/conditional/blueprints/dashboard.py b/conditional/blueprints/dashboard.py index c080ddaf..32587a77 100644 --- a/conditional/blueprints/dashboard.py +++ b/conditional/blueprints/dashboard.py @@ -12,7 +12,8 @@ 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.member import get_active_members, get_freshman_data, get_voting_members, get_cm, get_hm, req_cm +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, \ user_dict_is_onfloor @@ -146,4 +147,18 @@ def display_dashboard(user_dict=None): data['hm_attendance'] = hm_attendance data['hm_attendance_len'] = len(hm_attendance) + gatekeep_info = gatekeep_values(uid) + gatekeep_result = 'disenfranchised' + + if gatekeep_info['result']: + gatekeep_result = 'passing' + + data['gatekeep_active'] = is_gatekeep_active() + data['gatekeep'] = { + 'status': gatekeep_result, + 'committee_meetings': gatekeep_info['c_meetings'], + 'technical_seminars': gatekeep_info['t_seminars'], + 'hm_missed': gatekeep_info['h_meetings_missed'] + } + return render_template('dashboard.html', **data) diff --git a/conditional/templates/dashboard.html b/conditional/templates/dashboard.html index 693e228c..397c8ef4 100644 --- a/conditional/templates/dashboard.html +++ b/conditional/templates/dashboard.html @@ -233,7 +233,6 @@

Member Statistics

- {% if major_projects_count == 0 and not active%} {% elif major_projects_count > 0 %} @@ -318,6 +317,77 @@

Housing Status

+ {% if active %} +
+
+

Gatekeep + {% if gatekeep['status'] == "passing" %} + Ok! + {% elif gatekeep['status'] == "disenfranchised" and gatekeep_active%} + Disenfranchised + {% elif active %} + Pending + {% endif %} +

+
+
+ + + {% if not gatekeep_active %} + + + + + {% endif %} + + + + + + + + + + + + + +
Status + + Gatekeep Inactive Until 6 Weeks + +
Technical Seminars + + {% if gatekeep['technical_seminars'] >= 2 %} + + {% else %} + + {% endif %} + {{ gatekeep['technical_seminars'] }} / 2 + +
Directorship Meetings + + {% if gatekeep['committee_meetings'] >= 6 %} + + {% else %} + + {% endif %} + {{ gatekeep['committee_meetings']}} / 6 + +
House Meetings Missed + {% if gatekeep['hm_missed'] == 0 %} + None + {% elif gatekeep['hm_missed'] <= 1 %} + {{ gatekeep['hm_missed'] }} Missed + {% else %} + {{gatekeep['hm_missed']}} + {% endif %} + +
+
+
+ {% endif %} + {% if hm_attendance_len == 0 and active%}
You haven't missed any house meetings. diff --git a/conditional/util/member.py b/conditional/util/member.py index fd68205a..c859eddc 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -129,7 +129,6 @@ def get_cm(member): return c_meetings - def get_hm(member, only_absent=False): h_meetings = MemberHouseMeetingAttendance.query.outerjoin( HouseMeeting, @@ -144,7 +143,7 @@ def get_hm(member, only_absent=False): return h_meetings -# @service_cache(maxsize=128) +@service_cache(maxsize=128) 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. @@ -162,8 +161,15 @@ def req_cm(uid, members_on_coop=None): return 30 -@service_cache(maxsize=256) -def get_voting_members(): +def is_gatekeep_active(): + today = datetime.today() + 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()) + + return not (before_evals_one or before_evals_two) + + +def get_semester_info() -> tuple[str, datetime]: today = datetime.today() if today < datetime(start_of_year().year, 12, 31): semester = "Fall" @@ -172,6 +178,12 @@ def get_voting_members(): semester = "Spring" semester_start = datetime(start_of_year().year + 1, 1, 1) + return (semester, semester_start) + +@service_cache(maxsize=256) +def get_voting_members(): + semester, semester_start = get_semester_info() + active_members = get_active_members() intro_members = get_intro_members() @@ -205,9 +217,7 @@ def get_voting_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: + if not is_gatekeep_active(): return eligible_members passing_dm = set(member.uid for member in MemberCommitteeAttendance.query.join( @@ -265,23 +275,19 @@ def get_voting_members(): def gatekeep_status(username): - 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: + if not is_gatekeep_active(): 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) - else: - semester = "Spring" - semester_start = datetime(start_of_year().year + 1, 1, 1) + + return gatekeep_values(username) + + +def gatekeep_values(username): + semester, semester_start = get_semester_info() # groups ldap_member = ldap_get_member(username) @@ -346,6 +352,8 @@ def gatekeep_status(username): ) .count() ) + + h_meetings_missed = 1 result = eligibility_of_groups and (d_meetings >= 6 and t_seminars >= 2 and h_meetings_missed < 2) # pylint: disable=chained-comparison return { From 98bfac9fbc0619b231caa6ffe7638b3a8caeb080 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 15:00:06 -0500 Subject: [PATCH 2/9] gatekeep page --- conditional/__init__.py | 4 +- conditional/blueprints/gatekeep.py | 127 +++++++++++++++ conditional/templates/gatekeep.html | 190 +++++++++++++++++++++++ conditional/templates/nav.html | 1 + conditional/templates/nav_protected.html | 1 + conditional/util/user_dict.py | 1 + 6 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 conditional/blueprints/gatekeep.py create mode 100644 conditional/templates/gatekeep.html diff --git a/conditional/__init__.py b/conditional/__init__.py index 4d7cf1df..c2644948 100644 --- a/conditional/__init__.py +++ b/conditional/__init__.py @@ -118,6 +118,7 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse from .blueprints.intro_evals_form import intro_evals_form_bp from .blueprints.housing import housing_bp from .blueprints.spring_evals import spring_evals_bp +from .blueprints.gatekeep import gatekeep_bp from .blueprints.conditional import conditionals_bp from .blueprints.member_management import member_management_bp from .blueprints.slideshow import slideshow_bp @@ -132,6 +133,7 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse app.register_blueprint(intro_evals_form_bp) app.register_blueprint(housing_bp) app.register_blueprint(spring_evals_bp) +app.register_blueprint(gatekeep_bp) app.register_blueprint(conditionals_bp) app.register_blueprint(member_management_bp) app.register_blueprint(slideshow_bp) @@ -139,8 +141,6 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse app.register_blueprint(co_op_bp) app.register_blueprint(log_bp) -from .util.ldap import ldap_get_member - @app.route('/') def static_proxy(path): diff --git a/conditional/blueprints/gatekeep.py b/conditional/blueprints/gatekeep.py new file mode 100644 index 00000000..406c6282 --- /dev/null +++ b/conditional/blueprints/gatekeep.py @@ -0,0 +1,127 @@ +import structlog +from flask import Blueprint, request +from sqlalchemy import func + +from conditional import start_of_year, auth +from conditional.models.models import CommitteeMeeting, CurrentCoops, HouseMeeting, MemberCommitteeAttendance, MemberSeminarAttendance, TechnicalSeminar +from conditional.models.models import MemberHouseMeetingAttendance +from conditional.util.auth import get_user +from conditional.util.flask import render_template +from conditional.util.ldap import ldap_get_active_members +from conditional.util.member import get_semester_info + +gatekeep_bp = Blueprint('gatekeep_bp', __name__) + +logger = structlog.get_logger() + +@gatekeep_bp.route('/gatekeep_status/') +@auth.oidc_auth("default") +@get_user +def display_spring_evals(internal=False, user_dict=None): + log = logger.new(request=request, auth_dict=user_dict) + log.info('Display Gatekeep Status Listing') + + _, semester_start = get_semester_info() + active_members = ldap_get_active_members() + + cm_count = dict([tuple(row) for row in MemberCommitteeAttendance.query.join( + CommitteeMeeting, + MemberCommitteeAttendance.meeting_id == CommitteeMeeting.id + ).with_entities( + MemberCommitteeAttendance.uid, + CommitteeMeeting.timestamp, + CommitteeMeeting.approved, + ).filter( + CommitteeMeeting.approved, + CommitteeMeeting.timestamp >= semester_start + ).with_entities( + MemberCommitteeAttendance.uid, + func.count(MemberCommitteeAttendance.uid) #pylint: disable=not-callable + ).group_by( + MemberCommitteeAttendance.uid + ).all()]) + + ts_count = dict([tuple(row) for row in MemberSeminarAttendance.query.join( + TechnicalSeminar, + MemberSeminarAttendance.seminar_id == TechnicalSeminar.id + ).with_entities( + MemberSeminarAttendance.uid, + TechnicalSeminar.timestamp, + TechnicalSeminar.approved, + ).filter( + TechnicalSeminar.approved, + TechnicalSeminar.timestamp >= semester_start + ).with_entities( + MemberSeminarAttendance.uid, + func.count(MemberSeminarAttendance.uid) #pylint: disable=not-callable + ).group_by( + MemberSeminarAttendance.uid + ).all()]) + + hm_missed = dict([tuple(row) for row in MemberHouseMeetingAttendance.query.join( + HouseMeeting, + MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id + ).filter( + HouseMeeting.date >= semester_start, + MemberHouseMeetingAttendance.attendance_status == 'Absent' + ).with_entities( + MemberHouseMeetingAttendance.uid, + func.count(MemberHouseMeetingAttendance.uid) #pylint: disable=not-callable + ).group_by( + MemberHouseMeetingAttendance.uid + ).all()]) + + gk_members = [] + for account in active_members: + uid = account.uid + name = account.cn + + member_missed_hms = [] + + if hm_missed.get(uid, 0) != 0: + member_missed_hms = MemberHouseMeetingAttendance.query.join( + HouseMeeting, + MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id + ).filter( + HouseMeeting.date >= start_of_year(), + MemberHouseMeetingAttendance.attendance_status == 'Absent', + MemberHouseMeetingAttendance.uid == uid, + ).with_entities( + func.array_agg(HouseMeeting.date) + ).scalar() + + cm_attended_count = cm_count.get(uid, 0) + ts_attended_count = ts_count.get(uid, 0) + + passing = len(member_missed_hms) <= 1 and cm_attended_count >= 6 and ts_attended_count >= 2 + + status = 'disenfranchised' + + if passing: + status = 'passed' + + member = { + 'name': name, + 'uid': uid, + 'status': status, + 'committee_meetings': cm_attended_count, + 'technical_seminars': ts_attended_count, + 'req_meetings': 6, + 'req_seminars': 2, + 'house_meetings_missed': member_missed_hms, + } + + gk_members.append(member) + + gk_members.sort(key=lambda x: x['committee_meetings'], reverse=True) + gk_members.sort(key=lambda x: x['technical_seminars'], reverse=True) + gk_members.sort(key=lambda x: len(x['house_meetings_missed'])) + # return names in 'first last (username)' format + if internal: + return gk_members + + return render_template('gatekeep.html', + username=user_dict['username'], + members=gk_members, + req_meetings=6, + req_seminars=2) diff --git a/conditional/templates/gatekeep.html b/conditional/templates/gatekeep.html new file mode 100644 index 00000000..35013242 --- /dev/null +++ b/conditional/templates/gatekeep.html @@ -0,0 +1,190 @@ +{% extends "nav_protected.html" %} +{% block title %} +Gatekeep Results +{% endblock %} +{% block body %} +
+
+
+

Gatekeep Results

+
+
+
+
Table View
+ + +
+
+
+
+ {% for m in members %} +
+
+ + +
+
+
+
+ {{m['uid']}} + + {% if m['status'] == "passed" %} + + {% elif not gatekeep_active %} + + {% else %} + + {% endif %} + +
+
+
+

{{m['name']}}

+
{{m['uid']}}
+
+ +
+
+
+ {% if m['committee_meetings'] < req_meetings %} +
+ Directorship Meetings + {{m['committee_meetings']}} / {{ req_meetings }} +
+ {% else %} +
+ Directorship Meetings + {{m['committee_meetings']}} / {{ req_meetings }} +
+ {% endif %} +
+
+ {% if m['technical_seminars'] < req_seminars %} +
+ Technical Seminars + {{m['technical_seminars']}} / {{ req_seminars }} +
+ {% else %} +
+ Technical Seminars + {{m['technical_seminars']}} / {{ req_seminars }} +
+ {% endif %} +
+
+ {% if m['house_meetings_missed']|length > 1 %} +
+ House Meetings Missed + {{m['house_meetings_missed']|length}} +
+ {% else %} +
+ House Meetings Missed + {{m['house_meetings_missed']|length}} +
+ {% endif %} +
+
+
+
+ + {% if m['house_meetings_missed']|length > 0%} +
+ +
+ {% endif %} +
+ {% if m['house_meetings_missed']|length > 0 %} + +
+

Missed House Meetings

+ + + + + + + + + {% for hm_missed in m['house_meetings_missed'] %} + + + + + {% endfor %} + +
DateDescription
{{hm_missed}}
+ {% endif %} + +
+
+
+
+ + + {% endfor %} +
+ +
+{% endblock %} diff --git a/conditional/templates/nav.html b/conditional/templates/nav.html index 96049dc9..0b0b400b 100644 --- a/conditional/templates/nav.html +++ b/conditional/templates/nav.html @@ -18,6 +18,7 @@ diff --git a/conditional/templates/nav_protected.html b/conditional/templates/nav_protected.html index 7032353d..0e8bddc0 100644 --- a/conditional/templates/nav_protected.html +++ b/conditional/templates/nav_protected.html @@ -18,6 +18,7 @@ diff --git a/conditional/util/user_dict.py b/conditional/util/user_dict.py index bfc9ad3a..39e5b3fa 100644 --- a/conditional/util/user_dict.py +++ b/conditional/util/user_dict.py @@ -11,6 +11,7 @@ def user_dict_is_alumni(user_dict) -> bool: return not user_dict_is_active(user_dict) def user_dict_is_eboard(user_dict) -> bool: + return True return user_dict_is_in_group(user_dict, 'eboard') def user_dict_is_rtp(user_dict) -> bool: From 156c04a92ef11088b248fd607da2c5a1f5454f59 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 15:48:00 -0500 Subject: [PATCH 3/9] fix gatekeep page displaying the right thing when it's active --- conditional/blueprints/gatekeep.py | 5 ++++- conditional/templates/gatekeep.html | 12 +++++------- conditional/util/member.py | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/conditional/blueprints/gatekeep.py b/conditional/blueprints/gatekeep.py index 406c6282..42f9b390 100644 --- a/conditional/blueprints/gatekeep.py +++ b/conditional/blueprints/gatekeep.py @@ -8,7 +8,7 @@ from conditional.util.auth import get_user from conditional.util.flask import render_template from conditional.util.ldap import ldap_get_active_members -from conditional.util.member import get_semester_info +from conditional.util.member import get_semester_info, is_gatekeep_active gatekeep_bp = Blueprint('gatekeep_bp', __name__) @@ -120,8 +120,11 @@ def display_spring_evals(internal=False, user_dict=None): if internal: return gk_members + gatekeep_active = is_gatekeep_active() + return render_template('gatekeep.html', username=user_dict['username'], members=gk_members, + gatekeep_active=gatekeep_active, req_meetings=6, req_seminars=2) diff --git a/conditional/templates/gatekeep.html b/conditional/templates/gatekeep.html index 35013242..3ed0519d 100644 --- a/conditional/templates/gatekeep.html +++ b/conditional/templates/gatekeep.html @@ -9,19 +9,17 @@

Gatekeep Results

-
+
Table View
- - -
-
+ + +
+
{% for m in members %}
- -
diff --git a/conditional/util/member.py b/conditional/util/member.py index c859eddc..bc69936f 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -166,7 +166,7 @@ def is_gatekeep_active(): 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()) - return not (before_evals_one or before_evals_two) + return not (before_evals_one > 0 or before_evals_two > 0) def get_semester_info() -> tuple[str, datetime]: From ae0f47e6bb4da491f846499e94a5f1cc7157b111 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 15:56:53 -0500 Subject: [PATCH 4/9] Fix some formatting --- conditional/templates/gatekeep.html | 8 ++++---- conditional/util/user_dict.py | 1 - frontend/stylesheets/pages/_management.scss | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/conditional/templates/gatekeep.html b/conditional/templates/gatekeep.html index 3ed0519d..168eec94 100644 --- a/conditional/templates/gatekeep.html +++ b/conditional/templates/gatekeep.html @@ -6,7 +6,7 @@
-

Gatekeep Results

+

Gatekeep Results

@@ -27,11 +27,11 @@

Gatekeep Results

{{m['uid']}} {% if m['status'] == "passed" %} - + {% elif not gatekeep_active %} - + {% else %} - + {% endif %}
diff --git a/conditional/util/user_dict.py b/conditional/util/user_dict.py index 39e5b3fa..bfc9ad3a 100644 --- a/conditional/util/user_dict.py +++ b/conditional/util/user_dict.py @@ -11,7 +11,6 @@ def user_dict_is_alumni(user_dict) -> bool: return not user_dict_is_active(user_dict) def user_dict_is_eboard(user_dict) -> bool: - return True return user_dict_is_in_group(user_dict, 'eboard') def user_dict_is_rtp(user_dict) -> bool: diff --git a/frontend/stylesheets/pages/_management.scss b/frontend/stylesheets/pages/_management.scss index ba5d2d07..8a67cbe8 100644 --- a/frontend/stylesheets/pages/_management.scss +++ b/frontend/stylesheets/pages/_management.scss @@ -1,6 +1,6 @@ .switch-label { display: block; - padding: 17px 10px 10px; + padding: 0px 10px 10px; text-align: center; } From 3bba7828755f11f481d2d73187e483801ea493f6 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 16:01:10 -0500 Subject: [PATCH 5/9] fix linting --- conditional/blueprints/gatekeep.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conditional/blueprints/gatekeep.py b/conditional/blueprints/gatekeep.py index 42f9b390..f037516f 100644 --- a/conditional/blueprints/gatekeep.py +++ b/conditional/blueprints/gatekeep.py @@ -3,7 +3,8 @@ from sqlalchemy import func from conditional import start_of_year, auth -from conditional.models.models import CommitteeMeeting, CurrentCoops, HouseMeeting, MemberCommitteeAttendance, MemberSeminarAttendance, TechnicalSeminar +from conditional.models.models import CommitteeMeeting, HouseMeeting, MemberCommitteeAttendance, \ + MemberSeminarAttendance, TechnicalSeminar from conditional.models.models import MemberHouseMeetingAttendance from conditional.util.auth import get_user from conditional.util.flask import render_template From 995cb1334ad1e2c10bad34935753f536624679db Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 16:34:49 -0500 Subject: [PATCH 6/9] sonarqube for cole and stella --- .github/workflows/sonarqube.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index f7f96e00..5c157a50 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -4,6 +4,8 @@ on: push: branches: - develop + - master + pull_request: jobs: From 769fa77c205fc8f5f656d12b53d9e6a9936ae41c Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 16:50:21 -0500 Subject: [PATCH 7/9] trying to make sonarqube happy --- conditional/blueprints/gatekeep.py | 12 ++++++------ conditional/templates/gatekeep.html | 4 ++-- conditional/util/member.py | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/conditional/blueprints/gatekeep.py b/conditional/blueprints/gatekeep.py index f037516f..c7fb0ae6 100644 --- a/conditional/blueprints/gatekeep.py +++ b/conditional/blueprints/gatekeep.py @@ -25,7 +25,7 @@ def display_spring_evals(internal=False, user_dict=None): _, semester_start = get_semester_info() active_members = ldap_get_active_members() - cm_count = dict([tuple(row) for row in MemberCommitteeAttendance.query.join( + cm_count = {row[0]: row[1] for row in MemberCommitteeAttendance.query.join( CommitteeMeeting, MemberCommitteeAttendance.meeting_id == CommitteeMeeting.id ).with_entities( @@ -40,9 +40,9 @@ def display_spring_evals(internal=False, user_dict=None): func.count(MemberCommitteeAttendance.uid) #pylint: disable=not-callable ).group_by( MemberCommitteeAttendance.uid - ).all()]) + ).all()} - ts_count = dict([tuple(row) for row in MemberSeminarAttendance.query.join( + ts_count = {row[0]: row[1] for row in MemberSeminarAttendance.query.join( TechnicalSeminar, MemberSeminarAttendance.seminar_id == TechnicalSeminar.id ).with_entities( @@ -57,9 +57,9 @@ def display_spring_evals(internal=False, user_dict=None): func.count(MemberSeminarAttendance.uid) #pylint: disable=not-callable ).group_by( MemberSeminarAttendance.uid - ).all()]) + ).all()} - hm_missed = dict([tuple(row) for row in MemberHouseMeetingAttendance.query.join( + hm_missed = {row[0]: row[1] for row in MemberHouseMeetingAttendance.query.join( HouseMeeting, MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id ).filter( @@ -70,7 +70,7 @@ def display_spring_evals(internal=False, user_dict=None): func.count(MemberHouseMeetingAttendance.uid) #pylint: disable=not-callable ).group_by( MemberHouseMeetingAttendance.uid - ).all()]) + ).all()} gk_members = [] for account in active_members: diff --git a/conditional/templates/gatekeep.html b/conditional/templates/gatekeep.html index 168eec94..848f5f5f 100644 --- a/conditional/templates/gatekeep.html +++ b/conditional/templates/gatekeep.html @@ -88,9 +88,9 @@
{{m['uid']}}
{% if m['house_meetings_missed']|length > 0%}
-
{% endif %}
diff --git a/conditional/util/member.py b/conditional/util/member.py index bc69936f..f091cd4f 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -353,7 +353,6 @@ def gatekeep_values(username): .count() ) - h_meetings_missed = 1 result = eligibility_of_groups and (d_meetings >= 6 and t_seminars >= 2 and h_meetings_missed < 2) # pylint: disable=chained-comparison return { From 502fb2c1dbf7a74d197313f3829b006f64c188a8 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 17:00:23 -0500 Subject: [PATCH 8/9] more trying to make it happy --- conditional/templates/gatekeep.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conditional/templates/gatekeep.html b/conditional/templates/gatekeep.html index 848f5f5f..d5cb053e 100644 --- a/conditional/templates/gatekeep.html +++ b/conditional/templates/gatekeep.html @@ -88,7 +88,7 @@
{{m['uid']}}
{% if m['house_meetings_missed']|length > 0%}
-
From 4eb3dd29d17c38c3be6ae1de5549cd3633906a41 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Wed, 25 Feb 2026 17:21:40 -0500 Subject: [PATCH 9/9] update readme --- README.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 033275be..0a245208 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,16 @@ You can either develop using the dev database, or use the local database provide Using the local database is detailed below, but both options will require the dev database password, so you will have to ask an RTP for this too +#### Forcing evals/rtp or anything else +All of the role checking is done in `conditional/utils/user_dict.py`, and you can change the various functions to `return True` for debugging + ### Run (Without Docker) To run the application without using containers, you must have the latest version of [Python 3](https://www.python.org/downloads/) and [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) installed. Once you have those installed, create a new virtualenv and install the Python dependencies: ```sh -virtualenv .conditionalenv -p `which python3` -source .conditionalenv/bin/activate +virtualenv .venv +source .venv/bin/activate pip install -r requirements.txt ``` @@ -89,7 +92,9 @@ Which can be restarted every time changes are made 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. -### Local database +### Database Stuff + +#### Local database You can run the database locally using the docker compose @@ -106,18 +111,23 @@ To run migration commands in the local database, you can run the commands inside podman exec conditional flask db upgrade ``` -### Database Migrations +#### Database Migrations If the database schema is changed after initializing the database, you must migrate it to the new schema by running: -``` +```sh flask db upgrade +# or, to run it inside the container for use with local databases (DO THIS +podman exec conditional flask db upgrade ``` + At the same time, if you change the database schema, you must generate a new migration by running: -``` +```sh flask db migrate +# or, to run it inside the container for use with local databases (DO THIS +podman exec conditional flask db migrate ``` The new migration script in `migrations/versions` should be verified before being committed, as Alembic may not detect every change you make to the models. @@ -128,7 +138,7 @@ For more information, refer to the [Flask-Migrate](https://flask-migrate.readthe Conditional includes a utility to facilitate data migrations from the old Evals DB. This isn't necessary to run Conditional. To perform this migration, run the following commands before starting the application: -``` +```sh pip install pymysql flask zoo ```