diff --git a/README.md b/README.md index 1dcf9a51..61bf97ef 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,19 @@ 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. -after app initialization +### 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 diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 1927e440..a0973dfc 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -462,22 +462,20 @@ 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).first() + 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: @@ -487,6 +485,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/conditional/util/member.py b/conditional/util/member.py index b485d193..e9b28e3e 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -186,13 +186,13 @@ 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 # Check to see if there's an Intro Evals in the future of this semester. If there is, everyone gets to vote! 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, @@ -245,7 +245,7 @@ def get_voting_members(): passing_reqs = (passing_dm & passing_ts) - absent_hm - return elligible_members & passing_reqs + return eligible_members & passing_reqs def gatekeep_status(username): diff --git a/config.env.py b/config.env.py index c0ede697..705b2454 100644 --- a/config.env.py +++ b/config.env.py @@ -18,7 +18,7 @@ PROFILING = env.get("CONDITIONAL_PROFILING", "false").lower() == "true" # 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: + 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 ###