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
1 change: 1 addition & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- develop
- master
pull_request:


Expand Down
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -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
```
4 changes: 2 additions & 2 deletions conditional/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -132,15 +133,14 @@ 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)
app.register_blueprint(cache_bp)
app.register_blueprint(co_op_bp)
app.register_blueprint(log_bp)

from .util.ldap import ldap_get_member


@app.route('/<path:path>')
def static_proxy(path):
Expand Down
17 changes: 16 additions & 1 deletion conditional/blueprints/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
131 changes: 131 additions & 0 deletions conditional/blueprints/gatekeep.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import structlog
from flask import Blueprint, request
from sqlalchemy import func

from conditional import start_of_year, auth
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 conditional.util.ldap import ldap_get_active_members
from conditional.util.member import get_semester_info, is_gatekeep_active

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 = {row[0]: row[1] 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 = {row[0]: row[1] 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 = {row[0]: row[1] 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

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)
24 changes: 14 additions & 10 deletions conditional/blueprints/intro_evals.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
from conditional.util.auth import get_user
from conditional.util.flask import render_template
from conditional.util.ldap import ldap_get_intro_members
from conditional.util.member import get_semester_info

intro_evals_bp = Blueprint('intro_evals_bp', __name__)

logger = structlog.get_logger()

def get_intro_members_without_accounts():
_, semester_start = get_semester_info()
freshman_cm_count = dict([tuple(row) for row in FreshmanCommitteeAttendance.query.join(
CommitteeMeeting,
FreshmanCommitteeAttendance.meeting_id == CommitteeMeeting.id
Expand All @@ -34,7 +36,7 @@ def get_intro_members_without_accounts():
CommitteeMeeting.approved,
).filter(
CommitteeMeeting.approved,
CommitteeMeeting.timestamp >= start_of_year()
CommitteeMeeting.timestamp >= semester_start
).with_entities(
FreshmanCommitteeAttendance.fid,
func.count(FreshmanCommitteeAttendance.fid) #pylint: disable=not-callable
Expand Down Expand Up @@ -64,7 +66,7 @@ def get_intro_members_without_accounts():
TechnicalSeminar.approved
).filter(
TechnicalSeminar.approved,
TechnicalSeminar.timestamp >= start_of_year()
TechnicalSeminar.timestamp >= semester_start
).with_entities(
FreshmanSeminarAttendance.fid,
TechnicalSeminar.name
Expand All @@ -80,7 +82,7 @@ def get_intro_members_without_accounts():

# freshmen who don't have accounts
freshman_accounts = list(FreshmanAccount.query.filter(
FreshmanAccount.eval_date > start_of_year(),
FreshmanAccount.eval_date > semester_start,
FreshmanAccount.eval_date > datetime.now()))

ie_members = []
Expand All @@ -92,7 +94,7 @@ def get_intro_members_without_accounts():
HouseMeeting,
FreshmanHouseMeetingAttendance.meeting_id == HouseMeeting.id
).filter(
HouseMeeting.date >= start_of_year(), # TODO: this needs to be fixed
HouseMeeting.date >= semester_start,
FreshmanHouseMeetingAttendance.attendance_status == 'Absent',
FreshmanHouseMeetingAttendance.fid == freshman_account.id,
).with_entities(
Expand Down Expand Up @@ -135,6 +137,8 @@ def display_intro_evals(internal=False, user_dict=None):

ie_members = get_intro_members_without_accounts()

_, semester_start = get_semester_info()

account_cm_count = dict([tuple(row) for row in MemberCommitteeAttendance.query.join(
CommitteeMeeting,
MemberCommitteeAttendance.meeting_id == CommitteeMeeting.id
Expand All @@ -144,7 +148,7 @@ def display_intro_evals(internal=False, user_dict=None):
CommitteeMeeting.approved,
).filter(
CommitteeMeeting.approved,
CommitteeMeeting.timestamp >= start_of_year()
CommitteeMeeting.timestamp >= semester_start
).with_entities(
MemberCommitteeAttendance.uid,
func.count(MemberCommitteeAttendance.uid) #pylint: disable=not-callable
Expand All @@ -156,7 +160,7 @@ def display_intro_evals(internal=False, user_dict=None):
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
).filter(
HouseMeeting.date >= start_of_year(),
HouseMeeting.date >= semester_start,
MemberHouseMeetingAttendance.attendance_status == 'Absent'
).with_entities(
MemberHouseMeetingAttendance.uid,
Expand All @@ -174,7 +178,7 @@ def display_intro_evals(internal=False, user_dict=None):
TechnicalSeminar.approved
).filter(
TechnicalSeminar.approved,
TechnicalSeminar.timestamp >= start_of_year()
TechnicalSeminar.timestamp >= semester_start
).with_entities(
MemberSeminarAttendance.uid,
TechnicalSeminar.name
Expand All @@ -186,14 +190,14 @@ def display_intro_evals(internal=False, user_dict=None):
if not row[0] in account_ts_attendance_dict:
account_ts_attendance_dict[row[0]] = []

account_ts_attendance_dict[row[0]].append(row[1])
account_ts_attendance_dict[row[0]].append(row[1])

# freshmen who have accounts
for member in members:
uid = member.uid
name = member.cn
freshman_data = FreshmanEvalData.query.filter(
FreshmanEvalData.eval_date > start_of_year(),
FreshmanEvalData.eval_date > semester_start,
FreshmanEvalData.uid == uid).first()

if freshman_data is None:
Expand All @@ -208,7 +212,7 @@ def display_intro_evals(internal=False, user_dict=None):
HouseMeeting,
MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id
).filter(
HouseMeeting.date >= start_of_year(),
HouseMeeting.date >= semester_start,
MemberHouseMeetingAttendance.attendance_status == 'Absent',
MemberHouseMeetingAttendance.uid == uid,
).with_entities(
Expand Down
Loading