From f17b7dba0019c9518d256f97c38ff513b76cc21c Mon Sep 17 00:00:00 2001 From: Noah Date: Mon, 29 Dec 2025 15:06:07 -0500 Subject: [PATCH 1/6] attempt to fix upgrading accounts breaking frosh attendance --- conditional/blueprints/member_management.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 1927e440..27703afc 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -471,7 +471,8 @@ def member_management_upgrade_user(user_dict=None): for fhm in FreshmanHouseMeetingAttendance.query.filter(FreshmanHouseMeetingAttendance.fid == fid): # Don't duplicate HM attendance records mhm = MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id).first() + MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id and + MemberHouseMeetingAttendance.uid == uid).first() if mhm is None: db.session.add(MemberHouseMeetingAttendance( uid, fhm.meeting_id, fhm.excuse, fhm.attendance_status)) From 374c85390dced7d329ac86cbd294acc2c184dca0 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Tue, 3 Feb 2026 13:06:16 -0500 Subject: [PATCH 2/6] actually fix hm attendance and reorder opperations to create then delete --- conditional/blueprints/member_management.py | 9 +- conditional/models/models.py | 6 +- .../7a3904cac24b_freshmen_data_cascade.py | 36 ++++++ migrations/versions/e38beaf3e875_update_db.py | 111 ++++++++++++++++++ 4 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 migrations/versions/7a3904cac24b_freshmen_data_cascade.py create mode 100644 migrations/versions/e38beaf3e875_update_db.py diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 27703afc..35779852 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -462,23 +462,21 @@ def member_management_upgrade_user(user_dict=None): db.session.add(new_acct) for fca in FreshmanCommitteeAttendance.query.filter(FreshmanCommitteeAttendance.fid == fid): db.session.add(MemberCommitteeAttendance(uid, fca.meeting_id)) - db.session.delete(fca) for fts in FreshmanSeminarAttendance.query.filter(FreshmanSeminarAttendance.fid == fid): db.session.add(MemberSeminarAttendance(uid, fts.seminar_id)) - db.session.delete(fts) for fhm in FreshmanHouseMeetingAttendance.query.filter(FreshmanHouseMeetingAttendance.fid == fid): # Don't duplicate HM attendance records mhm = MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id and + MemberHouseMeetingAttendance.meeting_id == fhm.meeting_id, MemberHouseMeetingAttendance.uid == uid).first() if mhm is None: db.session.add(MemberHouseMeetingAttendance( uid, fhm.meeting_id, fhm.excuse, fhm.attendance_status)) + else: log.info(f'Duplicate house meeting attendance! fid: {fid}, uid: {uid}, id: {fhm.meeting_id}') - db.session.delete(fhm) new_account = ldap_get_member(uid) if acct.onfloor_status: @@ -488,6 +486,9 @@ def member_management_upgrade_user(user_dict=None): if acct.room_number: ldap_set_roomnumber(new_account, acct.room_number) + db.session.flush() + db.session.commit() + db.session.delete(acct) db.session.flush() diff --git a/conditional/models/models.py b/conditional/models/models.py index 97e8875f..69014b8a 100644 --- a/conditional/models/models.py +++ b/conditional/models/models.py @@ -83,7 +83,7 @@ def __init__(self, uid, meeting_id): class FreshmanCommitteeAttendance(db.Model): __tablename__ = 'freshman_committee_attendance' id = Column(Integer, primary_key=True) - fid = Column(ForeignKey('freshman_accounts.id'), nullable=False) + fid = Column(ForeignKey('freshman_accounts.id', ondelete="cascade"), nullable=False) meeting_id = Column(ForeignKey('committee_meetings.id'), nullable=False) def __init__(self, fid, meeting_id): @@ -120,7 +120,7 @@ def __init__(self, uid, seminar_id): class FreshmanSeminarAttendance(db.Model): __tablename__ = 'freshman_seminar_attendance' id = Column(Integer, primary_key=True) - fid = Column(ForeignKey('freshman_accounts.id'), nullable=False) + fid = Column(ForeignKey('freshman_accounts.id', ondelete="cascade"), nullable=False) seminar_id = Column(ForeignKey('technical_seminars.id'), nullable=False) def __init__(self, fid, seminar_id): @@ -178,7 +178,7 @@ def __init__(self, uid, meeting_id, excuse, status): class FreshmanHouseMeetingAttendance(db.Model): __tablename__ = 'freshman_hm_attendance' id = Column(Integer, primary_key=True) - fid = Column(ForeignKey('freshman_accounts.id'), nullable=False) + fid = Column(ForeignKey('freshman_accounts.id', ondelete="cascade"), nullable=False) meeting_id = Column(ForeignKey('house_meetings.id'), nullable=False) excuse = Column(Text) attendance_status = Column(attendance_enum) diff --git a/migrations/versions/7a3904cac24b_freshmen_data_cascade.py b/migrations/versions/7a3904cac24b_freshmen_data_cascade.py new file mode 100644 index 00000000..18cac087 --- /dev/null +++ b/migrations/versions/7a3904cac24b_freshmen_data_cascade.py @@ -0,0 +1,36 @@ +"""Freshmen data cascade + +Revision ID: 7a3904cac24b +Revises: e38beaf3e875 +Create Date: 2026-02-03 12:14:37.119352 + +""" + +# revision identifiers, used by Alembic. +revision = '7a3904cac24b' +down_revision = 'e38beaf3e875' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('freshman_committee_attendance_fid_fkey', 'freshman_committee_attendance', type_='foreignkey') + op.create_foreign_key(None, 'freshman_committee_attendance', 'freshman_accounts', ['fid'], ['id'], ondelete='cascade') + op.drop_constraint('freshman_hm_attendance_fid_fkey', 'freshman_hm_attendance', type_='foreignkey') + op.create_foreign_key(None, 'freshman_hm_attendance', 'freshman_accounts', ['fid'], ['id'], ondelete='cascade') + op.drop_constraint('freshman_seminar_attendance_fid_fkey', 'freshman_seminar_attendance', type_='foreignkey') + op.create_foreign_key(None, 'freshman_seminar_attendance', 'freshman_accounts', ['fid'], ['id'], ondelete='cascade') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'freshman_seminar_attendance', type_='foreignkey') + op.create_foreign_key('freshman_seminar_attendance_fid_fkey', 'freshman_seminar_attendance', 'freshman_accounts', ['fid'], ['id']) + op.drop_constraint(None, 'freshman_hm_attendance', type_='foreignkey') + op.create_foreign_key('freshman_hm_attendance_fid_fkey', 'freshman_hm_attendance', 'freshman_accounts', ['fid'], ['id']) + op.drop_constraint(None, 'freshman_committee_attendance', type_='foreignkey') + op.create_foreign_key('freshman_committee_attendance_fid_fkey', 'freshman_committee_attendance', 'freshman_accounts', ['fid'], ['id']) + # ### end Alembic commands ### diff --git a/migrations/versions/e38beaf3e875_update_db.py b/migrations/versions/e38beaf3e875_update_db.py new file mode 100644 index 00000000..2a367f9a --- /dev/null +++ b/migrations/versions/e38beaf3e875_update_db.py @@ -0,0 +1,111 @@ +"""update db + +Revision ID: e38beaf3e875 +Revises: 757e18146d16 +Create Date: 2026-02-03 12:12:11.451367 + +""" + +# revision identifiers, used by Alembic. +revision = 'e38beaf3e875' +down_revision = '757e18146d16' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index('member_batch_users_id_idx', table_name='member_batch_users') + op.drop_table('member_batch_users') + op.drop_index('freshman_batch_pulls_id_idx', table_name='freshman_batch_pulls') + op.drop_table('freshman_batch_pulls') + op.drop_index('member_batch_pulls_id_idx', table_name='member_batch_pulls') + op.drop_table('member_batch_pulls') + op.drop_index('freshman_batch_users_id_pkey', table_name='freshman_batch_users') + op.drop_table('freshman_batch_users') + op.drop_table('batch_conditions') + op.alter_column('freshman_accounts', 'onfloor_status', + existing_type=sa.BOOLEAN(), + nullable=True) + op.alter_column('freshman_accounts', 'rit_username', + existing_type=sa.VARCHAR(length=10), + nullable=True) + op.alter_column('freshman_hm_attendance', 'attendance_status', + existing_type=postgresql.ENUM('Attended', 'Excused', 'Absent', name='attendance_enum'), + nullable=True) + op.alter_column('member_hm_attendance', 'attendance_status', + existing_type=postgresql.ENUM('Attended', 'Excused', 'Absent', name='attendance_enum'), + nullable=True) + op.drop_table('batch') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('member_hm_attendance', 'attendance_status', + existing_type=postgresql.ENUM('Attended', 'Excused', 'Absent', name='attendance_enum'), + nullable=False) + op.alter_column('freshman_hm_attendance', 'attendance_status', + existing_type=postgresql.ENUM('Attended', 'Excused', 'Absent', name='attendance_enum'), + nullable=False) + op.alter_column('freshman_accounts', 'rit_username', + existing_type=sa.VARCHAR(length=10), + nullable=False) + op.alter_column('freshman_accounts', 'onfloor_status', + existing_type=sa.BOOLEAN(), + nullable=False) + op.create_table('batch_conditions', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('value', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('condition', postgresql.ENUM('packet', 'seminar', 'committee', 'house', name='batch_ctype_enum'), autoincrement=False, nullable=False), + sa.Column('comparison', postgresql.ENUM('less', 'equal', 'greater', name='batch_comparison'), autoincrement=False, nullable=False), + sa.Column('batch_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['batch_id'], ['batch.id'], name='batch_conditions_fk'), + sa.PrimaryKeyConstraint('id', name='batch_conditions_pkey') + ) + op.create_table('freshman_batch_users', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('fid', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('batch_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['batch_id'], ['batch.id'], name='freshman_batch_users_fk'), + sa.ForeignKeyConstraint(['fid'], ['freshman_accounts.id'], name='freshman_batch_users_fk_1'), + sa.PrimaryKeyConstraint('id', name='freshman_batch_users_pkey') + ) + op.create_index('freshman_batch_users_id_pkey', 'freshman_batch_users', ['id'], unique=True) + op.create_table('batch', + sa.Column('id', sa.INTEGER(), server_default=sa.text("nextval('batch_id_seq'::regclass)"), autoincrement=True, nullable=False), + sa.Column('name', sa.TEXT(), autoincrement=False, nullable=False), + sa.Column('uid', sa.VARCHAR(length=32), autoincrement=False, nullable=False), + sa.Column('approved', sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='batch_pkey'), + postgresql_ignore_search_path=False + ) + op.create_table('member_batch_pulls', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('uid', sa.VARCHAR(length=32), autoincrement=False, nullable=False), + sa.Column('approved', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False), + sa.Column('reason', sa.TEXT(), server_default=sa.text("''::text"), autoincrement=False, nullable=False), + sa.Column('puller', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='member_batch_pulls_pkey') + ) + op.create_index('member_batch_pulls_id_idx', 'member_batch_pulls', ['id'], unique=True) + op.create_table('freshman_batch_pulls', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('fid', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('approved', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False), + sa.Column('reason', sa.TEXT(), server_default=sa.text("''::text"), autoincrement=False, nullable=False), + sa.Column('puller', sa.VARCHAR(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['fid'], ['freshman_accounts.id'], name='freshman_batch_pulls_fk_1'), + sa.PrimaryKeyConstraint('id', name='freshman_batch_pulls_pkey') + ) + op.create_index('freshman_batch_pulls_id_idx', 'freshman_batch_pulls', ['id'], unique=True) + op.create_table('member_batch_users', + sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column('uid', sa.VARCHAR(length=32), autoincrement=False, nullable=False), + sa.Column('batch_id', sa.INTEGER(), autoincrement=False, nullable=False), + sa.ForeignKeyConstraint(['batch_id'], ['batch.id'], name='member_batch_users_fk'), + sa.PrimaryKeyConstraint('id', name='member_batch_users_pkey') + ) + op.create_index('member_batch_users_id_idx', 'member_batch_users', ['id'], unique=False) + # ### end Alembic commands ### From 8549130fef1571e7306ffe99cc8be854916708e3 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Tue, 3 Feb 2026 13:18:55 -0500 Subject: [PATCH 3/6] local db explaination --- README.md | 14 ++++++++++++++ config.env.py | 2 +- docker-compose.yaml | 24 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yaml diff --git a/README.md b/README.md index 7377f98a..61bf97ef 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,20 @@ This will run the asset pipeline, start the Python server, and start BrowserSync 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 + +You can run the database locally using the docker compose, make sure to upgrade it as explained below + +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 +``` + +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 + ### Database Migrations If the database schema is changed after initializing the database, you must migrate it to the new schema by running: diff --git a/config.env.py b/config.env.py index b05fe583..cbbc8afe 100644 --- a/config.env.py +++ b/config.env.py @@ -17,7 +17,7 @@ WEBHOOK_URL = env.get("CONDITIONAL_WEBHOOK_URL", "INSERT URL HERE") # DB Info -SQLALCHEMY_DATABASE_URI = env.get("SQLALCHEMY_DATABASE_URI", "") +SQLALCHEMY_DATABASE_URI = "postgresql://conditional:fancypantspassword@conditional-postgres:5432/conditional" SQLALCHEMY_TRACK_MODIFICATIONS = False # LDAP config diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..c5ff7a4b --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,24 @@ +version: "3" +services: + conditional: + build: . + container_name: conditional + depends_on: + - conditional-postgres + ports: + - "127.0.0.1:8080:8080" + conditional-postgres: + image: docker.io/postgres + container_name: conditional-postgres + environment: + POSTGRES_PASSWORD: fancypantspassword + POSTGRES_USER: conditional + POSTGRES_DATABASE: conditional + ports: + - "127.0.0.1:5432:5432" + volumes: + - pgdata:/var/lib/postgresql + +volumes: + pgdata: + From d78eb0c3488fdd29bef89ae53fa2dcfa84d69a5d Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Tue, 3 Feb 2026 13:29:13 -0500 Subject: [PATCH 4/6] trailing whitespace :)))))))) --- conditional/blueprints/member_management.py | 1 - 1 file changed, 1 deletion(-) diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 35779852..a0973dfc 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -474,7 +474,6 @@ def member_management_upgrade_user(user_dict=None): if mhm is None: db.session.add(MemberHouseMeetingAttendance( uid, fhm.meeting_id, fhm.excuse, fhm.attendance_status)) - else: log.info(f'Duplicate house meeting attendance! fid: {fid}, uid: {uid}, id: {fhm.meeting_id}') From 7f040f356d0f51d01ce6b0bab8c427c245fd43aa Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Thu, 5 Feb 2026 17:39:53 -0500 Subject: [PATCH 5/6] fix typo --- conditional/util/member.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conditional/util/member.py b/conditional/util/member.py index 8dc3d69a..f1122c07 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -183,7 +183,7 @@ def get_voting_members(): active_not_intro = active_members - intro_members active_not_intro = set(map(lambda member: member.uid, active_not_intro)) - elligible_members = (active_not_intro - coop_members) | passed_fall_members + eligible_members = (active_not_intro - coop_members) | passed_fall_members passing_dm = set(member.uid for member in MemberCommitteeAttendance.query.join( CommitteeMeeting, @@ -237,7 +237,7 @@ def get_voting_members(): passing_reqs = passing_dm & passing_ts & passing_hm - return elligible_members & passing_reqs + return eligible_members & passing_reqs def gatekeep_status(username): if datetime.today() < datetime(start_of_year().year, 12, 31): From ae3c87be5cc8126e0cbc6d822b5fea0057ca4b33 Mon Sep 17 00:00:00 2001 From: "Noah Hanford (spaced)" Date: Thu, 5 Feb 2026 17:42:50 -0500 Subject: [PATCH 6/6] fix variable name --- conditional/util/member.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conditional/util/member.py b/conditional/util/member.py index f5d49735..e9b28e3e 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -192,7 +192,7 @@ def get_voting_members(): 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: - return elligible_members + return eligible_members passing_dm = set(member.uid for member in MemberCommitteeAttendance.query.join( CommitteeMeeting,