Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6441972
Check if user is spring intro member so it returns the right number o…
charlottegeo Mar 27, 2026
47d5bfe
Automatically add 2 housing points when user passes spring evals (#511)
pikachu0542 Mar 30, 2026
05a839c
Bump cryptography from 46.0.5 to 46.0.6 (#533)
dependabot[bot] Mar 30, 2026
822044e
Track hosting Technical Seminars
Nil-32-0 Mar 31, 2026
1683af6
Bump sentry-sdk from 2.24.1 to 2.56.0 (#543)
dependabot[bot] Mar 31, 2026
0e05cc1
Bump click from 8.1.8 to 8.3.1 (#541)
dependabot[bot] Mar 31, 2026
d9d18ab
Bump flask-migrate from 2.1.1 to 4.1.0 (#540)
dependabot[bot] Mar 31, 2026
c383adf
Bump botocore from 1.35.13 to 1.35.99 (#542)
dependabot[bot] Mar 31, 2026
c1e6ae5
Bump gunicorn from 25.1.0 to 25.3.0 (#539)
dependabot[bot] Apr 1, 2026
7f401ec
Add missing display
Nil-32-0 Apr 1, 2026
51caa03
Merge branch 'develop' into dev-upstream
Nil-32-0 Apr 1, 2026
ea926f5
Merge pull request #2 from Nil-32-0/dev-upstream
Nil-32-0 Apr 1, 2026
b7dc9c7
Fix opening CM attendance
Nil-32-0 Apr 1, 2026
ccc802c
Hide host field when editing CM
Nil-32-0 Apr 1, 2026
91841dd
Add migration and fix element name
Nil-32-0 Apr 1, 2026
6be3864
No attendance credit given for hosting a seminar
Nil-32-0 Apr 7, 2026
c799850
Shift TS display into one column in Gatekeep table
Nil-32-0 Apr 7, 2026
7cea286
Make Pylint passing
Nil-32-0 Apr 11, 2026
78dd2e0
fix evals stuff (#552)
BigSpaceships Apr 11, 2026
af23e07
Merge branch 'develop' into develop
BigSpaceships Apr 11, 2026
241afaf
Merge pull request #545 from Nil-32-0/develop
tallen42 Apr 11, 2026
886a1bf
Remove ddtrace (#554)
BigSpaceships Apr 15, 2026
dfe4415
Bump sentry-sdk from 2.56.0 to 2.58.0 (#556)
dependabot[bot] Apr 22, 2026
1e9b8cf
Bump mako from 1.3.10 to 1.3.11 (#555)
dependabot[bot] Apr 22, 2026
5927f42
Bump boto3 from 1.35.13 to 1.35.99 (#549)
dependabot[bot] Apr 22, 2026
8893f03
Bump lodash from 4.17.23 to 4.18.1 (#553)
dependabot[bot] Apr 22, 2026
d6a6ad6
Bump @babel/core from 7.28.5 to 7.29.0 (#537)
dependabot[bot] Apr 22, 2026
e079c21
Bump css-loader from 7.1.2 to 7.1.4 (#538)
dependabot[bot] Apr 22, 2026
00c16e5
Bump @babel/preset-env from 7.28.5 to 7.29.2 (#536)
dependabot[bot] Apr 22, 2026
b2561c5
Bump sass-loader from 16.0.6 to 16.0.7 (#534)
dependabot[bot] Apr 22, 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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ var/
.installed.cfg
*.egg

.env

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down Expand Up @@ -69,6 +71,9 @@ target/
# Cloud9 IDE
/.c9

# VS Codepy
.vscode/

# Sensitive project files
/config.json
/config.py
Expand Down
95 changes: 48 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,23 @@ A comprehensive membership evaluations solution for Computer Science House.
Development
-----------

### Config
## Running (containerized)

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']
}
```

#### Add S3 Config
An S3 bucket is used to store files that users upload (currently just for major project submissions). In order to have this work properly, you need to provide some credentials to the app.
It is likely easier to use containers like `podman` or `docker` or the corresponding compose file

There are 2 ways that you can get the needed credentials.
1. Reach out to an RTP for creds to the dev bucket
2. Create your own bucket using [DEaDASS](https://deadass.csh.rit.edu/), and the site will give you the credentials you need.
With podman, I have been using

```py
S3_URI = env.get("S3_URI", "https://s3.csh.rit.edu")
S3_BUCKET_ID = env.get("S3_BUCKET_ID", "major-project-media")
AWS_ACCESS_KEY_ID = env.get("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = env.get("AWS_SECRET_ACCESS_KEY", "")
```sh
podman compose up --watch
```

#### 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
If you want, you can run without auto rebuild using
```sh
podman compose up --force-recreate --build
```
Which can be restarted every time changes are made.

### Run (Without Docker)
## 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:

Expand Down Expand Up @@ -90,30 +68,53 @@ or
python -m gunicorn
```

### Run (containerized)
## Config

It is likely easier to use containers like `podman` or `docker` or the corresponding compose file

With podman, I have been using
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.

```sh
podman compose up --watch
### 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']
}
```

If you want, you can run without compose support using
```sh
podman compose up --force-recreate --build
### Add S3 Config
An S3 bucket is used to store files that users upload (currently just for major project submissions). In order to have this work properly, you need to provide some credentials to the app.

There are 2 ways that you can get the needed credentials.
1. Reach out to an RTP for creds to the dev bucket
2. Create your own bucket using [DEaDASS](https://deadass.csh.rit.edu/), and the site will give you the credentials you need.

```py
S3_URI = env.get("S3_URI", "https://s3.csh.rit.edu")
S3_BUCKET_ID = env.get("S3_BUCKET_ID", "major-project-media")
AWS_ACCESS_KEY_ID = env.get("AWS_ACCESS_KEY_ID", "")
AWS_SECRET_ACCESS_KEY = env.get("AWS_SECRET_ACCESS_KEY", "")
```

Which can be restarted every time changes are made
### 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



### Dependencies
## 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.

### Database Stuff
## Database Stuff

#### Local database
### Local database

You can run the database locally using the docker compose

Expand All @@ -130,7 +131,7 @@ 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:

Expand Down
68 changes: 67 additions & 1 deletion conditional/blueprints/attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
from conditional.models.models import FreshmanCommitteeAttendance
from conditional.models.models import FreshmanHouseMeetingAttendance
from conditional.models.models import FreshmanSeminarAttendance
from conditional.models.models import FreshmanSeminarHost
from conditional.models.models import HouseMeeting
from conditional.models.models import MemberCommitteeAttendance
from conditional.models.models import MemberHouseMeetingAttendance
from conditional.models.models import MemberSeminarAttendance
from conditional.models.models import MemberSeminarHost
from conditional.models.models import TechnicalSeminar
from conditional.util.auth import get_user
from conditional.util.flask import render_template
Expand Down Expand Up @@ -218,6 +220,8 @@ def submit_seminar_attendance(user_dict=None):
seminar_name = post_data['name']
m_attendees = post_data['members']
f_attendees = post_data['freshmen']
m_host = post_data['member_host']
f_host = post_data['freshman_host']
timestamp = post_data['timestamp']

timestamp = datetime.strptime(timestamp, "%Y-%m-%d")
Expand All @@ -228,12 +232,24 @@ def submit_seminar_attendance(user_dict=None):
db.session.refresh(seminar)

for m in m_attendees:
if m in m_host:
log.info(f'Skipped giving Attendence to {m} for {seminar_name}')
continue
log.info(f'Gave Attendance to {m} for {seminar_name}')
db.session.add(MemberSeminarAttendance(m, seminar.id))
for m in m_host:
log.info(f'Gave Host Credit to {m} for {seminar_name}')
db.session.add(MemberSeminarHost(m, seminar.id))

for f in f_attendees:
if f in f_host:
log.info(f'Skipped giving Attendance to freshman-{f} for {seminar_name}')
continue
log.info(f'Gave Attendance to freshman-{f} for {seminar_name}')
db.session.add(FreshmanSeminarAttendance(f, seminar.id))
for f in f_host:
log.info(f'Gave Host Credit to freshman-{f} for {seminar_name}')
db.session.add(FreshmanSeminarHost(f, seminar.id))

db.session.commit()
return jsonify({"success": True}), 200
Expand Down Expand Up @@ -379,6 +395,18 @@ def get_seminar_attendees(meeting_id):
FreshmanAccount.id == freshman).first().name)
return attendees

def get_seminar_hosts(meeting_id):
hosts = [ldap_get_member(a.uid).displayName for a in
MemberSeminarHost.query.filter(
MemberSeminarHost.seminar_id == meeting_id).all()]

for freshman in [a.fid for a in
FreshmanSeminarHost.query.filter(
FreshmanSeminarHost.seminar_id == meeting_id).all()]:
hosts.append(FreshmanAccount.query.filter(
FreshmanAccount.id == freshman).first().name)
return hosts

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

if not user_dict_is_eboard(user_dict):
Expand All @@ -402,6 +430,7 @@ def get_seminar_attendees(meeting_id):
"name": m.name,
"dt_obj": m.timestamp,
"date": m.timestamp.strftime("%a %m/%d/%Y"),
"hosts": get_seminar_hosts(m.id),
"attendees": get_seminar_attendees(m.id),
"type": "ts"
} for m in TechnicalSeminar.query.filter(
Expand All @@ -419,6 +448,7 @@ def get_seminar_attendees(meeting_id):
"name": m.name,
"dt_obj": m.timestamp,
"date": m.timestamp.strftime("%a %m/%d/%Y"),
"hosts": get_seminar_hosts(m.id),
"attendees": get_seminar_attendees(m.id)
} for m in TechnicalSeminar.query.filter(
TechnicalSeminar.timestamp > start_of_year(),
Expand All @@ -433,6 +463,7 @@ def get_seminar_attendees(meeting_id):
history=all_meetings,
pending_cm=pend_cm,
pending_ts=pend_ts,
all_ts=all_ts,
num_pages=total_pages,
current_page=int(page))

Expand Down Expand Up @@ -483,19 +514,37 @@ def alter_seminar_attendance(sid, user_dict=None):
meeting_id = sid
m_attendees = post_data['members']
f_attendees = post_data['freshmen']
m_host = post_data['member_host']
f_host = post_data['freshman_host']

FreshmanSeminarAttendance.query.filter(
FreshmanSeminarAttendance.seminar_id == meeting_id).delete()

FreshmanSeminarHost.query.filter(
FreshmanSeminarHost.seminar_id == meeting_id).delete()

MemberSeminarAttendance.query.filter(
MemberSeminarAttendance.seminar_id == meeting_id).delete()

MemberSeminarHost.query.filter(
MemberSeminarHost.seminar_id == meeting_id).delete()

for m in m_attendees:
if m in m_host:
continue
db.session.add(MemberSeminarAttendance(m, meeting_id))

for m in m_host:
db.session.add(MemberSeminarHost(m, meeting_id))

for f in f_attendees:
if f in f_host:
continue
db.session.add(FreshmanSeminarAttendance(f, meeting_id))

for f in f_host:
db.session.add(FreshmanSeminarHost(f, meeting_id))

db.session.flush()
db.session.commit()
return jsonify({"success": True}), 200
Expand All @@ -512,12 +561,25 @@ def get_cm_attendees(sid, user_dict=None):
MemberSeminarAttendance.query.filter(
MemberSeminarAttendance.seminar_id == sid).all()]

host = [{"value": a.uid,
"display": ldap_get_member(a.uid).displayName
} for a in
MemberSeminarHost.query.filter(
MemberSeminarHost.seminar_id == sid).all()]

for freshman in [{"value": a.fid,
"display": FreshmanAccount.query.filter(FreshmanAccount.id == a.fid).first().name
} for a in FreshmanSeminarAttendance.query.filter(
FreshmanSeminarAttendance.seminar_id == sid).all()]:
attendees.append(freshman)
return jsonify({"attendees": attendees}), 200

for freshman in [{"value": a.fid,
"display": FreshmanAccount.query.filter(FreshmanAccount.id == a.fid).first().name
} for a in FreshmanSeminarHost.query.filter(
FreshmanSeminarHost.seminar_id == sid).all()]:
host.append(freshman)

return jsonify({"attendees": attendees, "host": host}), 200

log = logger.new(request=request, auth_dict=user_dict)
log.info(f'Delete Technical Seminar {sid}')
Expand All @@ -527,8 +589,12 @@ def get_cm_attendees(sid, user_dict=None):

FreshmanSeminarAttendance.query.filter(
FreshmanSeminarAttendance.seminar_id == sid).delete()
FreshmanSeminarHost.query.filter(
FreshmanSeminarHost.seminar_id == sid).delete()
MemberSeminarAttendance.query.filter(
MemberSeminarAttendance.seminar_id == sid).delete()
MemberSeminarHost.query.filter(
MemberSeminarHost.seminar_id == sid).delete()
TechnicalSeminar.query.filter(
TechnicalSeminar.id == sid).delete()

Expand Down
8 changes: 8 additions & 0 deletions conditional/blueprints/conditional.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from conditional.models.models import Conditional, SpringEval, FreshmanEvalData
from conditional.util.auth import get_user
from conditional.util.flask import render_template
from conditional.util.ldap import ldap_get_member, ldap_set_housingpoints
from conditional.util.user_dict import user_dict_is_eval_director

conditionals_bp = Blueprint('conditionals_bp', __name__)
Expand Down Expand Up @@ -91,6 +92,7 @@ def conditional_review(user_dict=None):
log.info(f'Updated conditional-{cid} to {status}')
conditional = Conditional.query.filter(Conditional.id == cid)
cond_obj = conditional.first()
uid = cond_obj.uid

conditional.update(
{
Expand All @@ -101,6 +103,12 @@ def conditional_review(user_dict=None):
{
'status': status
})

if status == 'Passed':
account = ldap_get_member(uid)
hp = int(account.housingPoints)
ldap_set_housingpoints(account, str(hp + 2))

elif cond_obj.i_evaluation:
FreshmanEvalData.query.filter(FreshmanEvalData.id == cond_obj.i_evaluation).update(
{
Expand Down
12 changes: 12 additions & 0 deletions conditional/blueprints/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from conditional.models.models import HouseMeeting
from conditional.models.models import MemberHouseMeetingAttendance
from conditional.models.models import MemberSeminarAttendance
from conditional.models.models import MemberSeminarHost
from conditional.models.models import TechnicalSeminar
from conditional.models.models import SpringEval
from conditional.util.auth import get_user
Expand Down Expand Up @@ -106,11 +107,21 @@ def display_dashboard(user_dict=None):
MemberSeminarAttendance.uid == uid,
) if is_seminar_attendance_valid(s)]
data['ts_total'] = len(t_seminars)
# technical seminars hosted
t_seminars_hosted = [s.seminar_id for s in
MemberSeminarHost.query.filter(
MemberSeminarHost.uid == uid,
) if is_seminar_attendance_valid(s)]
data['ts_hosted_total'] = len(t_seminars_hosted)
attendance = [m.name for m in TechnicalSeminar.query.filter(
TechnicalSeminar.id.in_(t_seminars)
)]
hosted = [m.name for m in TechnicalSeminar.query.filter(
TechnicalSeminar.id.in_(t_seminars_hosted)
)]

data['ts_list'] = attendance
data['ts_hosted'] = hosted

spring['mp_status'] = "Failed"
for mp in data['major_projects']:
Expand Down Expand Up @@ -164,6 +175,7 @@ def display_dashboard(user_dict=None):
'status': gatekeep_result,
'committee_meetings': gatekeep_info['c_meetings'],
'technical_seminars': gatekeep_info['t_seminars'],
'technical_seminars_hosted': gatekeep_info['t_seminars_hosted'],
'hm_missed': gatekeep_info['h_meetings_missed']
}

Expand Down
Loading
Loading