mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
feat: add Decision column to TUVote
In preparation for allowing TUs to change their votes on proposals, we need a way to track what users vote for. Without this, the vote decisions are stored within the related TU_VoteInfo record, decoupled from the user who made the vote. That being the case meant we cannot actually change a vote, because we can't figure out what TU_VoteInfo decision columns to decrement when the vote has changed. You may be wondering why we aren't removing the decision columns out of TU_VoteInfo with the advent of this new column. The reason being: previous votes are all calculated off of the TU_VoteInfo columns, so without a manual DB rework, relocating them to the user-related vote records would break outcomes of old proposals. So, the plan is: we'll solely use this column for votes from this point on to track what decision a user made internally, which will open up TUs changing their decision. In addition, this migration resets all running proposals: - all votes are deleted - time is reset to Start when migration is run This was necessary to put running proposals into a state that can take advantage of the new revote system. Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
parent
c7c79a152b
commit
a29701459c
4 changed files with 95 additions and 4 deletions
|
@ -6,6 +6,17 @@ from aurweb.models.declarative import Base
|
|||
from aurweb.models.tu_voteinfo import TUVoteInfo as _TUVoteInfo
|
||||
from aurweb.models.user import User as _User
|
||||
|
||||
YES_ID = 1
|
||||
NO_ID = 2
|
||||
ABSTAIN_ID = 3
|
||||
|
||||
DECISIONS = {
|
||||
YES_ID: "Yes",
|
||||
NO_ID: "No",
|
||||
ABSTAIN_ID: "Abstain",
|
||||
}
|
||||
DECISION_IDS = {v: k for k, v in DECISIONS.items()}
|
||||
|
||||
|
||||
class TUVote(Base):
|
||||
__table__ = schema.TU_Votes
|
||||
|
@ -29,10 +40,17 @@ class TUVote(Base):
|
|||
raise IntegrityError(
|
||||
statement="Foreign key VoteID cannot be null.",
|
||||
orig="TU_Votes.VoteID",
|
||||
params=("NULL"))
|
||||
params=("NULL",))
|
||||
|
||||
if not self.User and not self.UserID:
|
||||
raise IntegrityError(
|
||||
statement="Foreign key UserID cannot be null.",
|
||||
orig="TU_Votes.UserID",
|
||||
params=("NULL"))
|
||||
params=("NULL",))
|
||||
|
||||
if self.Decision is None or self.Decision not in DECISIONS:
|
||||
raise IntegrityError(
|
||||
statement=("Decision column must be a valid key in "
|
||||
"aurweb.models.tu_vote.DECISIONS"),
|
||||
orig="TU_Votes.Decision",
|
||||
params=(self.Decision,))
|
||||
|
|
|
@ -406,6 +406,7 @@ TU_Votes = Table(
|
|||
'TU_Votes', metadata,
|
||||
Column('VoteID', ForeignKey('TU_VoteInfo.ID', ondelete='CASCADE'), nullable=False),
|
||||
Column('UserID', ForeignKey('Users.ID', ondelete='CASCADE'), nullable=False),
|
||||
Column('Decision', TINYINT(unsigned=True)),
|
||||
mysql_engine='InnoDB',
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
"""relocate vote decisions into TUVote
|
||||
|
||||
Revision ID: 84cd90073dde
|
||||
Revises: d64e5571bc8d
|
||||
Create Date: 2022-03-07 18:02:28.791103
|
||||
|
||||
This migration allows us to give Trusted Users the ability to
|
||||
modify a vote they made on a proposal. Previously, the decision
|
||||
was tracked purely through TU_VoteInfo records, which removes
|
||||
tracking of what decisions users made. This blocks us from being
|
||||
able to modify votes, because we can't update the TU_VoteInfo
|
||||
Yes, No or Abstain columns properly.
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects.mysql import TINYINT
|
||||
|
||||
from aurweb import db, logging, time
|
||||
from aurweb.models import TUVoteInfo
|
||||
|
||||
logger = logging.get_logger("alembic")
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '84cd90073dde'
|
||||
down_revision = 'd64e5571bc8d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
TABLE = "TU_Votes"
|
||||
|
||||
|
||||
def upgrade():
|
||||
decision = sa.Column("Decision", TINYINT(unsigned=True))
|
||||
op.add_column(TABLE, decision)
|
||||
|
||||
# For each proposal which is running at the time this migration
|
||||
# is applied, eradicate all related votes. We will restart the votes.
|
||||
# In addition, reset the Submitted and End columns based on the current
|
||||
# timestamp; essentially resetting the proposal's state.
|
||||
utcnow = time.utcnow()
|
||||
running_proposals = db.query(TUVoteInfo).filter(TUVoteInfo.End > utcnow)
|
||||
with db.begin():
|
||||
for proposal in running_proposals:
|
||||
logger.info(f"Resetting proposal with ID {proposal.ID}: "
|
||||
"Yes = 0, No = 0, Abstain = 0, deleted vote records")
|
||||
|
||||
# Reset the Submitted and End columns.
|
||||
length = proposal.End - proposal.Submitted
|
||||
proposal.Submitted = utcnow
|
||||
proposal.End = utcnow + length
|
||||
|
||||
proposal.Yes = proposal.No = proposal.Abstain = 0
|
||||
db.delete_all(proposal.tu_votes)
|
||||
logger.info(f"Proposal time range was reset: Submitted = "
|
||||
f"{proposal.Submitted} and End = {proposal.End}")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column(TABLE, "Decision")
|
|
@ -4,7 +4,7 @@ from sqlalchemy.exc import IntegrityError
|
|||
|
||||
from aurweb import db, time
|
||||
from aurweb.models.account_type import TRUSTED_USER_ID
|
||||
from aurweb.models.tu_vote import TUVote
|
||||
from aurweb.models.tu_vote import YES_ID, TUVote
|
||||
from aurweb.models.tu_voteinfo import TUVoteInfo
|
||||
from aurweb.models.user import User
|
||||
|
||||
|
@ -36,10 +36,12 @@ def tu_voteinfo(user: User) -> TUVoteInfo:
|
|||
|
||||
def test_tu_vote_creation(user: User, tu_voteinfo: TUVoteInfo):
|
||||
with db.begin():
|
||||
tu_vote = db.create(TUVote, User=user, VoteInfo=tu_voteinfo)
|
||||
tu_vote = db.create(TUVote, User=user, VoteInfo=tu_voteinfo,
|
||||
Decision=YES_ID)
|
||||
|
||||
assert tu_vote.VoteInfo == tu_voteinfo
|
||||
assert tu_vote.User == user
|
||||
assert tu_vote.Decision == YES_ID
|
||||
assert tu_vote in user.tu_votes
|
||||
assert tu_vote in tu_voteinfo.tu_votes
|
||||
|
||||
|
@ -52,3 +54,12 @@ def test_tu_vote_null_user_raises_exception(tu_voteinfo: TUVoteInfo):
|
|||
def test_tu_vote_null_voteinfo_raises_exception(user: User):
|
||||
with pytest.raises(IntegrityError):
|
||||
TUVote(User=user)
|
||||
|
||||
|
||||
def test_tu_vote_bad_decision_raises_exception(user: User,
|
||||
tu_voteinfo: TUVoteInfo):
|
||||
with pytest.raises(IntegrityError):
|
||||
TUVote(User=user, VoteInfo=tu_voteinfo, Decision=None)
|
||||
|
||||
with pytest.raises(IntegrityError):
|
||||
TUVote(User=user, VoteInfo=tu_voteinfo, Decision=0)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue