Skip to content

Commit b4fe087

Browse files
authored
Merge pull request #489 from ComputerScienceHouse/gatekeep-on-dashboard
Gatekeep on dashboard and gatekeep page
2 parents 3858339 + 33ddcc6 commit b4fe087

12 files changed

Lines changed: 468 additions & 40 deletions

File tree

.github/workflows/sonarqube.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- develop
7+
- master
78
pull_request:
89

910

README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ You can either develop using the dev database, or use the local database provide
2929

3030
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
3131

32+
#### Forcing evals/rtp or anything else
33+
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
34+
3235
### Run (Without Docker)
3336

3437
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:
3538

3639
```sh
37-
virtualenv .conditionalenv -p `which python3`
38-
source .conditionalenv/bin/activate
40+
virtualenv .venv
41+
source .venv/bin/activate
3942
pip install -r requirements.txt
4043
```
4144

@@ -89,7 +92,9 @@ Which can be restarted every time changes are made
8992

9093
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.
9194

92-
### Local database
95+
### Database Stuff
96+
97+
#### Local database
9398

9499
You can run the database locally using the docker compose
95100

@@ -106,18 +111,23 @@ To run migration commands in the local database, you can run the commands inside
106111
podman exec conditional flask db upgrade
107112
```
108113

109-
### Database Migrations
114+
#### Database Migrations
110115

111116
If the database schema is changed after initializing the database, you must migrate it to the new schema by running:
112117

113-
```
118+
```sh
114119
flask db upgrade
120+
# or, to run it inside the container for use with local databases (DO THIS
121+
podman exec conditional flask db upgrade
115122
```
116123

124+
117125
At the same time, if you change the database schema, you must generate a new migration by running:
118126

119-
```
127+
```sh
120128
flask db migrate
129+
# or, to run it inside the container for use with local databases (DO THIS
130+
podman exec conditional flask db migrate
121131
```
122132

123133
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
128138

129139
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:
130140

131-
```
141+
```sh
132142
pip install pymysql
133143
flask zoo
134144
```

conditional/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse
118118
from .blueprints.intro_evals_form import intro_evals_form_bp
119119
from .blueprints.housing import housing_bp
120120
from .blueprints.spring_evals import spring_evals_bp
121+
from .blueprints.gatekeep import gatekeep_bp
121122
from .blueprints.conditional import conditionals_bp
122123
from .blueprints.member_management import member_management_bp
123124
from .blueprints.slideshow import slideshow_bp
@@ -132,15 +133,14 @@ def database_processor(logger, log_method, event_dict): # pylint: disable=unuse
132133
app.register_blueprint(intro_evals_form_bp)
133134
app.register_blueprint(housing_bp)
134135
app.register_blueprint(spring_evals_bp)
136+
app.register_blueprint(gatekeep_bp)
135137
app.register_blueprint(conditionals_bp)
136138
app.register_blueprint(member_management_bp)
137139
app.register_blueprint(slideshow_bp)
138140
app.register_blueprint(cache_bp)
139141
app.register_blueprint(co_op_bp)
140142
app.register_blueprint(log_bp)
141143

142-
from .util.ldap import ldap_get_member
143-
144144

145145
@app.route('/<path:path>')
146146
def static_proxy(path):

conditional/blueprints/dashboard.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from conditional.util.auth import get_user
1313
from conditional.util.flask import render_template
1414
from conditional.util.housing import get_queue_position
15-
from conditional.util.member import get_active_members, get_freshman_data, get_voting_members, get_cm, get_hm, req_cm
15+
from conditional.util.member import gatekeep_values, get_active_members, get_freshman_data, get_voting_members, \
16+
get_cm, get_hm, is_gatekeep_active, req_cm
1617
from conditional.util.user_dict import user_dict_is_active, user_dict_is_bad_standing, user_dict_is_intromember, \
1718
user_dict_is_onfloor
1819

@@ -146,4 +147,18 @@ def display_dashboard(user_dict=None):
146147
data['hm_attendance'] = hm_attendance
147148
data['hm_attendance_len'] = len(hm_attendance)
148149

150+
gatekeep_info = gatekeep_values(uid)
151+
gatekeep_result = 'disenfranchised'
152+
153+
if gatekeep_info['result']:
154+
gatekeep_result = 'passing'
155+
156+
data['gatekeep_active'] = is_gatekeep_active()
157+
data['gatekeep'] = {
158+
'status': gatekeep_result,
159+
'committee_meetings': gatekeep_info['c_meetings'],
160+
'technical_seminars': gatekeep_info['t_seminars'],
161+
'hm_missed': gatekeep_info['h_meetings_missed']
162+
}
163+
149164
return render_template('dashboard.html', **data)

conditional/blueprints/gatekeep.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import structlog
2+
from flask import Blueprint, request
3+
from sqlalchemy import func
4+
5+
from conditional import start_of_year, auth
6+
from conditional.models.models import CommitteeMeeting, HouseMeeting, MemberCommitteeAttendance, \
7+
MemberSeminarAttendance, TechnicalSeminar
8+
from conditional.models.models import MemberHouseMeetingAttendance
9+
from conditional.util.auth import get_user
10+
from conditional.util.flask import render_template
11+
from conditional.util.ldap import ldap_get_active_members
12+
from conditional.util.member import get_semester_info, is_gatekeep_active
13+
14+
gatekeep_bp = Blueprint('gatekeep_bp', __name__)
15+
16+
logger = structlog.get_logger()
17+
18+
@gatekeep_bp.route('/gatekeep_status/')
19+
@auth.oidc_auth("default")
20+
@get_user
21+
def display_spring_evals(internal=False, user_dict=None):
22+
log = logger.new(request=request, auth_dict=user_dict)
23+
log.info('Display Gatekeep Status Listing')
24+
25+
_, semester_start = get_semester_info()
26+
active_members = ldap_get_active_members()
27+
28+
cm_count = {row[0]: row[1] for row in MemberCommitteeAttendance.query.join(
29+
CommitteeMeeting,
30+
MemberCommitteeAttendance.meeting_id == CommitteeMeeting.id
31+
).with_entities(
32+
MemberCommitteeAttendance.uid,
33+
CommitteeMeeting.timestamp,
34+
CommitteeMeeting.approved,
35+
).filter(
36+
CommitteeMeeting.approved,
37+
CommitteeMeeting.timestamp >= semester_start
38+
).with_entities(
39+
MemberCommitteeAttendance.uid,
40+
func.count(MemberCommitteeAttendance.uid) #pylint: disable=not-callable
41+
).group_by(
42+
MemberCommitteeAttendance.uid
43+
).all()}
44+
45+
ts_count = {row[0]: row[1] for row in MemberSeminarAttendance.query.join(
46+
TechnicalSeminar,
47+
MemberSeminarAttendance.seminar_id == TechnicalSeminar.id
48+
).with_entities(
49+
MemberSeminarAttendance.uid,
50+
TechnicalSeminar.timestamp,
51+
TechnicalSeminar.approved,
52+
).filter(
53+
TechnicalSeminar.approved,
54+
TechnicalSeminar.timestamp >= semester_start
55+
).with_entities(
56+
MemberSeminarAttendance.uid,
57+
func.count(MemberSeminarAttendance.uid) #pylint: disable=not-callable
58+
).group_by(
59+
MemberSeminarAttendance.uid
60+
).all()}
61+
62+
hm_missed = {row[0]: row[1] for row in MemberHouseMeetingAttendance.query.join(
63+
HouseMeeting,
64+
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
65+
).filter(
66+
HouseMeeting.date >= semester_start,
67+
MemberHouseMeetingAttendance.attendance_status == 'Absent'
68+
).with_entities(
69+
MemberHouseMeetingAttendance.uid,
70+
func.count(MemberHouseMeetingAttendance.uid) #pylint: disable=not-callable
71+
).group_by(
72+
MemberHouseMeetingAttendance.uid
73+
).all()}
74+
75+
gk_members = []
76+
for account in active_members:
77+
uid = account.uid
78+
name = account.cn
79+
80+
member_missed_hms = []
81+
82+
if hm_missed.get(uid, 0) != 0:
83+
member_missed_hms = MemberHouseMeetingAttendance.query.join(
84+
HouseMeeting,
85+
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
86+
).filter(
87+
HouseMeeting.date >= start_of_year(),
88+
MemberHouseMeetingAttendance.attendance_status == 'Absent',
89+
MemberHouseMeetingAttendance.uid == uid,
90+
).with_entities(
91+
func.array_agg(HouseMeeting.date)
92+
).scalar()
93+
94+
cm_attended_count = cm_count.get(uid, 0)
95+
ts_attended_count = ts_count.get(uid, 0)
96+
97+
passing = len(member_missed_hms) <= 1 and cm_attended_count >= 6 and ts_attended_count >= 2
98+
99+
status = 'disenfranchised'
100+
101+
if passing:
102+
status = 'passed'
103+
104+
member = {
105+
'name': name,
106+
'uid': uid,
107+
'status': status,
108+
'committee_meetings': cm_attended_count,
109+
'technical_seminars': ts_attended_count,
110+
'req_meetings': 6,
111+
'req_seminars': 2,
112+
'house_meetings_missed': member_missed_hms,
113+
}
114+
115+
gk_members.append(member)
116+
117+
gk_members.sort(key=lambda x: x['committee_meetings'], reverse=True)
118+
gk_members.sort(key=lambda x: x['technical_seminars'], reverse=True)
119+
gk_members.sort(key=lambda x: len(x['house_meetings_missed']))
120+
# return names in 'first last (username)' format
121+
if internal:
122+
return gk_members
123+
124+
gatekeep_active = is_gatekeep_active()
125+
126+
return render_template('gatekeep.html',
127+
username=user_dict['username'],
128+
members=gk_members,
129+
gatekeep_active=gatekeep_active,
130+
req_meetings=6,
131+
req_seminars=2)

conditional/blueprints/intro_evals.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
from conditional.util.auth import get_user
2020
from conditional.util.flask import render_template
2121
from conditional.util.ldap import ldap_get_intro_members
22+
from conditional.util.member import get_semester_info
2223

2324
intro_evals_bp = Blueprint('intro_evals_bp', __name__)
2425

2526
logger = structlog.get_logger()
2627

2728
def get_intro_members_without_accounts():
29+
_, semester_start = get_semester_info()
2830
freshman_cm_count = dict([tuple(row) for row in FreshmanCommitteeAttendance.query.join(
2931
CommitteeMeeting,
3032
FreshmanCommitteeAttendance.meeting_id == CommitteeMeeting.id
@@ -34,7 +36,7 @@ def get_intro_members_without_accounts():
3436
CommitteeMeeting.approved,
3537
).filter(
3638
CommitteeMeeting.approved,
37-
CommitteeMeeting.timestamp >= start_of_year()
39+
CommitteeMeeting.timestamp >= semester_start
3840
).with_entities(
3941
FreshmanCommitteeAttendance.fid,
4042
func.count(FreshmanCommitteeAttendance.fid) #pylint: disable=not-callable
@@ -64,7 +66,7 @@ def get_intro_members_without_accounts():
6466
TechnicalSeminar.approved
6567
).filter(
6668
TechnicalSeminar.approved,
67-
TechnicalSeminar.timestamp >= start_of_year()
69+
TechnicalSeminar.timestamp >= semester_start
6870
).with_entities(
6971
FreshmanSeminarAttendance.fid,
7072
TechnicalSeminar.name
@@ -80,7 +82,7 @@ def get_intro_members_without_accounts():
8082

8183
# freshmen who don't have accounts
8284
freshman_accounts = list(FreshmanAccount.query.filter(
83-
FreshmanAccount.eval_date > start_of_year(),
85+
FreshmanAccount.eval_date > semester_start,
8486
FreshmanAccount.eval_date > datetime.now()))
8587

8688
ie_members = []
@@ -92,7 +94,7 @@ def get_intro_members_without_accounts():
9294
HouseMeeting,
9395
FreshmanHouseMeetingAttendance.meeting_id == HouseMeeting.id
9496
).filter(
95-
HouseMeeting.date >= start_of_year(), # TODO: this needs to be fixed
97+
HouseMeeting.date >= semester_start,
9698
FreshmanHouseMeetingAttendance.attendance_status == 'Absent',
9799
FreshmanHouseMeetingAttendance.fid == freshman_account.id,
98100
).with_entities(
@@ -135,6 +137,8 @@ def display_intro_evals(internal=False, user_dict=None):
135137

136138
ie_members = get_intro_members_without_accounts()
137139

140+
_, semester_start = get_semester_info()
141+
138142
account_cm_count = dict([tuple(row) for row in MemberCommitteeAttendance.query.join(
139143
CommitteeMeeting,
140144
MemberCommitteeAttendance.meeting_id == CommitteeMeeting.id
@@ -144,7 +148,7 @@ def display_intro_evals(internal=False, user_dict=None):
144148
CommitteeMeeting.approved,
145149
).filter(
146150
CommitteeMeeting.approved,
147-
CommitteeMeeting.timestamp >= start_of_year()
151+
CommitteeMeeting.timestamp >= semester_start
148152
).with_entities(
149153
MemberCommitteeAttendance.uid,
150154
func.count(MemberCommitteeAttendance.uid) #pylint: disable=not-callable
@@ -156,7 +160,7 @@ def display_intro_evals(internal=False, user_dict=None):
156160
HouseMeeting,
157161
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
158162
).filter(
159-
HouseMeeting.date >= start_of_year(),
163+
HouseMeeting.date >= semester_start,
160164
MemberHouseMeetingAttendance.attendance_status == 'Absent'
161165
).with_entities(
162166
MemberHouseMeetingAttendance.uid,
@@ -174,7 +178,7 @@ def display_intro_evals(internal=False, user_dict=None):
174178
TechnicalSeminar.approved
175179
).filter(
176180
TechnicalSeminar.approved,
177-
TechnicalSeminar.timestamp >= start_of_year()
181+
TechnicalSeminar.timestamp >= semester_start
178182
).with_entities(
179183
MemberSeminarAttendance.uid,
180184
TechnicalSeminar.name
@@ -186,14 +190,14 @@ def display_intro_evals(internal=False, user_dict=None):
186190
if not row[0] in account_ts_attendance_dict:
187191
account_ts_attendance_dict[row[0]] = []
188192

189-
account_ts_attendance_dict[row[0]].append(row[1])
193+
account_ts_attendance_dict[row[0]].append(row[1])
190194

191195
# freshmen who have accounts
192196
for member in members:
193197
uid = member.uid
194198
name = member.cn
195199
freshman_data = FreshmanEvalData.query.filter(
196-
FreshmanEvalData.eval_date > start_of_year(),
200+
FreshmanEvalData.eval_date > semester_start,
197201
FreshmanEvalData.uid == uid).first()
198202

199203
if freshman_data is None:
@@ -208,7 +212,7 @@ def display_intro_evals(internal=False, user_dict=None):
208212
HouseMeeting,
209213
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
210214
).filter(
211-
HouseMeeting.date >= start_of_year(),
215+
HouseMeeting.date >= semester_start,
212216
MemberHouseMeetingAttendance.attendance_status == 'Absent',
213217
MemberHouseMeetingAttendance.uid == uid,
214218
).with_entities(

0 commit comments

Comments
 (0)