Skip to content
Merged

Dev #488

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c4a03d6
Gatekeep fix (#465)
tallen42 Feb 5, 2026
8c637fb
Fix frosh attendance (#464)
BigSpaceships Feb 13, 2026
45c3351
guys i'm dumb
BigSpaceships Feb 13, 2026
0171a65
Merge branch 'master' into develop
tallen42 Feb 18, 2026
7bbcf77
Bump ddtrace from 3.2.3 to 4.1.0 (#450)
dependabot[bot] Feb 18, 2026
ed9df98
Bump cryptography from 46.0.3 to 46.0.5 (#475)
dependabot[bot] Feb 18, 2026
18f1161
Bump wheel from 0.45.1 to 0.46.2 (#476)
dependabot[bot] Feb 18, 2026
a0968cc
fixed not all frosh attendance was showing up (#472)
BigSpaceships Feb 20, 2026
4714fa7
moved gunicorn config out of dockerfile args (#477)
BigSpaceships Feb 20, 2026
69d7848
Fixed redirect_uri
pikachu0542 Feb 20, 2026
d3f014c
Merge pull request #478 from ComputerScienceHouse/fix-redirect-uri
pikachu0542 Feb 20, 2026
ea77e7d
Speedup pt 2 (#469)
BigSpaceships Feb 23, 2026
4b553b6
Bump isort from 4.3.21 to 6.1.0 (#482)
dependabot[bot] Feb 23, 2026
d3c9aec
Bump werkzeug from 3.1.5 to 3.1.6 (#480)
dependabot[bot] Feb 23, 2026
65da067
Bump mako from 1.0.14 to 1.3.10 (#484)
dependabot[bot] Feb 23, 2026
1a1d5f7
Bump mccabe from 0.6.1 to 0.7.0 (#483)
dependabot[bot] Feb 23, 2026
aefd56f
Bump structlog from 18.1.0 to 25.5.0 (#481)
dependabot[bot] Feb 23, 2026
636dff4
slack: Added more to Slack message (#385)
atauln Feb 23, 2026
b05bdd6
added gatekeep page as currently implemented in constitution
BigSpaceships Feb 25, 2026
98bfac9
gatekeep page
BigSpaceships Feb 25, 2026
156c04a
fix gatekeep page displaying the right thing when it's active
BigSpaceships Feb 25, 2026
ae0f47e
Fix some formatting
BigSpaceships Feb 25, 2026
3bba782
fix linting
BigSpaceships Feb 25, 2026
995cb13
sonarqube for cole and stella
BigSpaceships Feb 25, 2026
769fa77
trying to make sonarqube happy
BigSpaceships Feb 25, 2026
502fb2c
more trying to make it happy
BigSpaceships Feb 25, 2026
4eb3dd2
update readme
BigSpaceships Feb 25, 2026
3858339
Add PR support to sonarqube workflow
Will-Hellinger Feb 26, 2026
639df03
Merge branch 'develop' into gatekeep-on-dashboard
tallen42 Feb 28, 2026
33ddcc6
fix frosh stuff more (#490)
BigSpaceships Feb 28, 2026
b4fe087
Merge pull request #489 from ComputerScienceHouse/gatekeep-on-dashboard
tallen42 Feb 28, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
push:
branches:
- develop
- master
pull_request:


jobs:
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ COPY --from=build-frontend /opt/conditional/conditional/static /opt/conditional/

RUN ln -sf /usr/share/zoneinfo/America/New_York /etc/localtime

CMD ["sh", "-c", "gunicorn conditional:app --bind=0.0.0.0:${PORT} --access-logfile=- --timeout=256"]
CMD ["sh", "-c", "gunicorn"]

109 changes: 75 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,85 +8,126 @@ A comprehensive membership evaluations solution for Computer Science House.
Development
-----------

To run the application, 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:
### Config

You must create `config.py` in the top-level directory with the appropriate credentials for the application to run. See `config.env.py` for an example.

#### Add OIDC Config
Reach out to an RTP to get OIDC credentials that will allow you to develop locally behind OIDC auth
```py
# OIDC Config
OIDC_ISSUER = "https://sso.csh.rit.edu/auth/realms/csh"
OIDC_CLIENT_CONFIG = {
'client_id': '',
'client_secret': '',
'post_logout_redirect_uris': ['http://0.0.0.0:6969/logout']
}
```
virtualenv .conditionalenv -p `which python3`
source .conditionalenv/bin/activate

#### Database
You can either develop using the dev database, or use the local database provided in the docker compose file

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 .venv
source .venv/bin/activate
pip install -r requirements.txt
export FLASK_APP=app.py
```

In addition, you must have Node, NPM, and Gulp CLI installed to properly execute the asset pipeline. If you don't have Node installed, we recommending installing with [NVM](https://github.com/creationix/nvm):
In addition, you must have Node, NPM, and Weback CLI installed to properly execute the asset pipeline. If you don't have Node installed, we recommending installing with [NVM](https://github.com/creationix/nvm):

```
```sh
nvm install
nvm use
npm install -g gulp
npm install -g webpack
```

Then, install the pipeline and frontend dependencies:
Then, install the pipeline and frontend dependencies: (do this in the `frontend` directory)

```
```sh
npm install
```

### Config
Once you have all of the dependencies installed, run

You must create `config.py` in the top-level directory with the appropriate credentials for the application to run. See `config.sample.py` for an example.
```sh
npm webpack
```

#### Add OIDC Config
Reach out to an RTP to get OIDC credentials that will allow you to develop locally behind OIDC auth
This will build the frontend assets and put them in the correct place for use with flask

Finally, start the flask app with `gunicorn`

```sh
gunicorn
```
# OIDC Config
OIDC_ISSUER = "https://sso.csh.rit.edu/auth/realms/csh"
OIDC_CLIENT_CONFIG = {
'client_id': '',
'client_secret': '',
'post_logout_redirect_uris': ['http://0.0.0.0:6969/logout']
}

or

```sh
python -m gunicorn
```

### Run
### Run (containerized)

Once you have all of the dependencies installed, simply run:
It is likely easier to use containers like `podman` or `docker` or the corresponding compose file

```
npm start
With podman, I have been using

```sh
podman compose up --force-recreate --build
```

This will run the asset pipeline, start the Python server, and start BrowserSync. Your default web browser will open automatically. If it doesn't, navigate to `http://127.0.0.1:3000`. Any changes made to the frontend files in `frontend` or the Jinja templates in `conditional/templates` will cause the browser to reload automatically.
Which can be restarted every time changes are made

### Dependencies

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

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

You can run the database locally using the docker compose

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
```sh
PGPASSWORD='[DB PASSWORD]' pg_dump -h postgres.csh.rit.edu -p 5432 -U conditionaldev conditionaldev | 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
To run migration commands in the local database, you can run the commands inside the docker container. Any migrations created will also be in the local repository since migrations are mounted in the docker compose
```sh
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 @@ -97,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
```
10 changes: 6 additions & 4 deletions conditional/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from datetime import datetime

from pstats import SortKey
import structlog
from csh_ldap import CSHLDAP
from flask import Flask, redirect, render_template, request, g
Expand Down Expand Up @@ -33,7 +34,8 @@
if app.config['PROFILING']:
app.wsgi_app = ProfilerMiddleware(
app.wsgi_app,
restrictions=[30]
sort_by=('cumulative',),
restrictions=[80]
)

# Sentry setup
Expand Down Expand Up @@ -116,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 @@ -130,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 Expand Up @@ -195,7 +197,7 @@ def route_errors(error, user_dict=None):

# Handle the case where the header isn't present
if user_dict['username'] is not None:
data['username'] = user_dict['account'].uid
data['username'] = user_dict['username']
data['name'] = user_dict['account'].cn
else:
data['username'] = "unknown"
Expand Down
26 changes: 13 additions & 13 deletions conditional/blueprints/attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from conditional.util.ldap import ldap_get_current_students
from conditional.util.ldap import ldap_get_member
from conditional.util.ldap import ldap_is_eboard
from conditional.util.ldap import ldap_is_eval_director
from conditional.util.user_dict import user_dict_is_eboard, user_dict_is_eval_director

logger = structlog.get_logger()

Expand Down Expand Up @@ -160,7 +160,7 @@ def display_attendance_hm(user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info('Display House Meeting Attendance Page')

if not ldap_is_eval_director(user_dict['account']):
if not user_dict_is_eval_director(user_dict):
return redirect("/dashboard")

return render_template('attendance_hm.html',
Expand All @@ -175,7 +175,7 @@ def display_attendance_hm(user_dict=None):
def submit_committee_attendance(user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)

approved = ldap_is_eboard(user_dict['account'])
approved = user_dict_is_eval_director(user_dict)
post_data = request.get_json()

committee = post_data['committee']
Expand Down Expand Up @@ -211,7 +211,7 @@ def submit_seminar_attendance(user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info('Submit Technical Seminar Attendance')

approved = ldap_is_eboard(user_dict['account'])
approved = user_dict_is_eboard(user_dict)

post_data = request.get_json()

Expand Down Expand Up @@ -248,7 +248,7 @@ def submit_house_attendance(user_dict=None):

# status: Attended | Excused | Absent

if not ldap_is_eval_director(user_dict['account']):
if not user_dict_is_eval_director(user_dict):
return "must be evals", 403

post_data = request.get_json()
Expand Down Expand Up @@ -289,7 +289,7 @@ def submit_house_attendance(user_dict=None):
def alter_house_attendance(uid, hid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)

if not ldap_is_eval_director(user_dict['account']):
if not user_dict_is_eval_director(user_dict):
return "must be evals", 403

if not uid.isdigit():
Expand Down Expand Up @@ -319,7 +319,7 @@ def alter_house_attendance(uid, hid, user_dict=None):
def alter_house_excuse(uid, hid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)

if not ldap_is_eval_director(user_dict['account']):
if not user_dict_is_eval_director(user_dict):
return "must be eval director", 403

post_data = request.get_json()
Expand Down Expand Up @@ -381,7 +381,7 @@ def get_seminar_attendees(meeting_id):

log = logger.new(request=request, auth_dict=user_dict)

if not ldap_is_eboard(user_dict['account']):
if not user_dict_is_eboard(user_dict):
return jsonify({"success": False, "error": "Not EBoard"}), 403


Expand Down Expand Up @@ -444,7 +444,7 @@ def alter_committee_attendance(cid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info('Edit Committee Meeting Attendance')

if not ldap_is_eboard(user_dict['account']):
if not user_dict_is_eboard(user_dict):
return jsonify({"success": False, "error": "Not EBoard"}), 403

post_data = request.get_json()
Expand Down Expand Up @@ -476,7 +476,7 @@ def alter_seminar_attendance(sid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info('Edit Technical Seminar Attendance')

if not ldap_is_eboard(user_dict['account']):
if not user_dict_is_eboard(user_dict):
return jsonify({"success": False, "error": "Not EBoard"}), 403

post_data = request.get_json()
Expand Down Expand Up @@ -559,7 +559,7 @@ def get_ts_attendees(cid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info(f'Delete Committee Meeting {cid}')

if not ldap_is_eboard(user_dict['account']):
if not user_dict_is_eboard(user_dict):
return jsonify({"success": False, "error": "Not EBoard"}), 403

FreshmanCommitteeAttendance.query.filter(
Expand All @@ -582,7 +582,7 @@ def approve_cm(cid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info(f'Approve Committee Meeting {cid} Attendance')

if not ldap_is_eboard(user_dict['account']):
if not user_dict_is_eboard(user_dict):
return jsonify({"success": False, "error": "Not EBoard"}), 403

CommitteeMeeting.query.filter(
Expand All @@ -600,7 +600,7 @@ def approve_ts(sid, user_dict=None):
log = logger.new(request=request, auth_dict=user_dict)
log.info(f'Approve Technical Seminar {sid} Attendance')

if not ldap_is_eboard(user_dict['account']):
if not user_dict_is_eboard(user_dict):
return jsonify({"success": False, "error": "Not EBoard"}), 403

TechnicalSeminar.query.filter(
Expand Down
7 changes: 3 additions & 4 deletions conditional/blueprints/cache_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
from conditional.util.ldap import ldap_get_intro_members
from conditional.util.ldap import ldap_get_member
from conditional.util.ldap import ldap_get_onfloor_members
from conditional.util.ldap import ldap_is_eval_director
from conditional.util.ldap import ldap_is_rtp
from conditional.util.user_dict import user_dict_is_eval_director, user_dict_is_rtp

logger = structlog.get_logger()
cache_bp = Blueprint('cache_bp', __name__)
Expand All @@ -24,7 +23,7 @@
@auth.oidc_auth("default")
@get_user
def restart_app(user_dict=None):
if not ldap_is_rtp(user_dict['account']):
if not user_dict_is_rtp(user_dict):
return redirect("/dashboard")

log = logger.new(request=request, auth_dict=user_dict)
Expand All @@ -37,7 +36,7 @@ def restart_app(user_dict=None):
@auth.oidc_auth("default")
@get_user
def clear_cache(user_dict=None):
if not ldap_is_eval_director(user_dict['account']) and not ldap_is_rtp(user_dict['account']):
if not user_dict_is_eval_director(user_dict) and not user_dict_is_rtp(user_dict):
return redirect("/dashboard")

log = logger.new(request=request, auth_dict=user_dict)
Expand Down
Loading