Add right/wrong marking with steal mechanism and question stats
Adds Correct/Wrong buttons to admin panel that track question answer stats across games and implement a steal system where the next team gets one chance to answer if the first team is wrong. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,9 @@ class Question(db.Model):
|
|||||||
start_time = db.Column(db.Integer, nullable=True) # Start time in seconds
|
start_time = db.Column(db.Integer, nullable=True) # Start time in seconds
|
||||||
end_time = db.Column(db.Integer, nullable=True) # End time in seconds
|
end_time = db.Column(db.Integer, nullable=True) # End time in seconds
|
||||||
|
|
||||||
|
times_correct = db.Column(db.Integer, default=0, nullable=False)
|
||||||
|
times_incorrect = db.Column(db.Integer, default=0, nullable=False)
|
||||||
|
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
||||||
|
|
||||||
@@ -109,6 +112,8 @@ class Question(db.Model):
|
|||||||
'start_time': self.start_time,
|
'start_time': self.start_time,
|
||||||
'end_time': self.end_time,
|
'end_time': self.end_time,
|
||||||
'category': self.category,
|
'category': self.category,
|
||||||
|
'times_correct': self.times_correct,
|
||||||
|
'times_incorrect': self.times_incorrect,
|
||||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||||
'created_by': self.created_by,
|
'created_by': self.created_by,
|
||||||
'creator_name': (self.creator.name or self.creator.preferred_username) if self.creator else None,
|
'creator_name': (self.creator.name or self.creator.preferred_username) if self.creator else None,
|
||||||
@@ -157,6 +162,7 @@ class Game(db.Model):
|
|||||||
current_question_index = db.Column(db.Integer, default=0) # Track current question
|
current_question_index = db.Column(db.Integer, default=0) # Track current question
|
||||||
is_template = db.Column(db.Boolean, default=False) # Mark as reusable template
|
is_template = db.Column(db.Boolean, default=False) # Mark as reusable template
|
||||||
current_turn_team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) # Track whose turn it is
|
current_turn_team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) # Track whose turn it is
|
||||||
|
steal_active = db.Column(db.Boolean, default=False) # True when a team got it wrong and next team can steal
|
||||||
completed_at = db.Column(db.DateTime, nullable=True) # When game ended
|
completed_at = db.Column(db.DateTime, nullable=True) # When game ended
|
||||||
winners = db.Column(db.JSON, nullable=True) # [{"team_name": str, "score": int}, ...]
|
winners = db.Column(db.JSON, nullable=True) # [{"team_name": str, "score": int}, ...]
|
||||||
|
|
||||||
@@ -192,6 +198,7 @@ class Game(db.Model):
|
|||||||
'is_template': self.is_template,
|
'is_template': self.is_template,
|
||||||
'current_turn_team_id': self.current_turn_team_id,
|
'current_turn_team_id': self.current_turn_team_id,
|
||||||
'current_turn_team_name': self.current_turn_team.name if self.current_turn_team else None,
|
'current_turn_team_name': self.current_turn_team.name if self.current_turn_team else None,
|
||||||
|
'steal_active': self.steal_active,
|
||||||
'completed_at': self.completed_at.isoformat() if self.completed_at else None,
|
'completed_at': self.completed_at.isoformat() if self.completed_at else None,
|
||||||
'winners': self.winners
|
'winners': self.winners
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,6 +304,43 @@ def seek_audio(game_id):
|
|||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/game/<int:game_id>/mark-correct', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def mark_correct(game_id):
|
||||||
|
"""Mark current question as answered correctly by the current turn team"""
|
||||||
|
game = Game.query.get_or_404(game_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
team = game_service.mark_correct(game, socketio)
|
||||||
|
if team:
|
||||||
|
return jsonify({'message': 'Marked correct', 'team': team.to_dict()}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({'error': 'No current question or team'}), 400
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/game/<int:game_id>/mark-wrong', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def mark_wrong(game_id):
|
||||||
|
"""Mark current question as answered incorrectly by the current turn team"""
|
||||||
|
game = Game.query.get_or_404(game_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = game_service.mark_wrong(game, socketio)
|
||||||
|
return jsonify({
|
||||||
|
'message': 'Marked wrong',
|
||||||
|
'steal_active': result['steal_active'],
|
||||||
|
'both_failed': result['both_failed']
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@bp.route('/game/<int:game_id>/advance-turn', methods=['POST'])
|
@bp.route('/game/<int:game_id>/advance-turn', methods=['POST'])
|
||||||
@require_auth
|
@require_auth
|
||||||
def advance_turn(game_id):
|
def advance_turn(game_id):
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ def get_game_state(game):
|
|||||||
'is_active': game.is_active,
|
'is_active': game.is_active,
|
||||||
'teams': [team.to_dict() for team in game.teams],
|
'teams': [team.to_dict() for team in game.teams],
|
||||||
'current_turn_team_id': game.current_turn_team_id,
|
'current_turn_team_id': game.current_turn_team_id,
|
||||||
'current_turn_team_name': game.current_turn_team.name if game.current_turn_team else None
|
'current_turn_team_name': game.current_turn_team.name if game.current_turn_team else None,
|
||||||
|
'steal_active': game.steal_active
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_question:
|
if current_question:
|
||||||
@@ -49,6 +50,7 @@ def start_game(game, socketio_instance):
|
|||||||
|
|
||||||
game.is_active = True
|
game.is_active = True
|
||||||
game.current_question_index = 0
|
game.current_question_index = 0
|
||||||
|
game.steal_active = False
|
||||||
|
|
||||||
# Set initial turn to the first team (by ID order)
|
# Set initial turn to the first team (by ID order)
|
||||||
ordered_teams = get_ordered_teams(game)
|
ordered_teams = get_ordered_teams(game)
|
||||||
@@ -83,8 +85,10 @@ def next_question(game, socketio_instance):
|
|||||||
"""Move to next question"""
|
"""Move to next question"""
|
||||||
if game.current_question_index < len(game.game_questions) - 1:
|
if game.current_question_index < len(game.game_questions) - 1:
|
||||||
game.current_question_index += 1
|
game.current_question_index += 1
|
||||||
|
game.steal_active = False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
broadcast_question_change(game, socketio_instance)
|
broadcast_question_change(game, socketio_instance)
|
||||||
|
broadcast_steal_state(game, socketio_instance)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -93,8 +97,10 @@ def previous_question(game, socketio_instance):
|
|||||||
"""Move to previous question"""
|
"""Move to previous question"""
|
||||||
if game.current_question_index > 0:
|
if game.current_question_index > 0:
|
||||||
game.current_question_index -= 1
|
game.current_question_index -= 1
|
||||||
|
game.steal_active = False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
broadcast_question_change(game, socketio_instance)
|
broadcast_question_change(game, socketio_instance)
|
||||||
|
broadcast_steal_state(game, socketio_instance)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -254,6 +260,7 @@ def restart_game(game, socketio_instance):
|
|||||||
game.is_active = False
|
game.is_active = False
|
||||||
game.current_question_index = 0
|
game.current_question_index = 0
|
||||||
game.current_turn_team_id = None # Reset turn
|
game.current_turn_team_id = None # Reset turn
|
||||||
|
game.steal_active = False
|
||||||
|
|
||||||
# Reset phone-a-friend lifelines for all teams
|
# Reset phone-a-friend lifelines for all teams
|
||||||
for team in game.teams:
|
for team in game.teams:
|
||||||
@@ -365,6 +372,76 @@ def advance_turn(game, socketio_instance):
|
|||||||
return next_team
|
return next_team
|
||||||
|
|
||||||
|
|
||||||
|
def mark_correct(game, socketio_instance):
|
||||||
|
"""Mark the current question as answered correctly by the current turn team.
|
||||||
|
Awards 1 point, increments question stats, resets steal state."""
|
||||||
|
current_question = game.get_current_question()
|
||||||
|
if not current_question:
|
||||||
|
return None
|
||||||
|
|
||||||
|
team = game.current_turn_team
|
||||||
|
if not team:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Increment question stats
|
||||||
|
current_question.times_correct += 1
|
||||||
|
|
||||||
|
# Reset steal state
|
||||||
|
game.steal_active = False
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Award 1 point
|
||||||
|
award_points(game, team, 1, socketio_instance)
|
||||||
|
|
||||||
|
# Broadcast steal state reset
|
||||||
|
broadcast_steal_state(game, socketio_instance)
|
||||||
|
|
||||||
|
return team
|
||||||
|
|
||||||
|
|
||||||
|
def mark_wrong(game, socketio_instance):
|
||||||
|
"""Mark the current question as answered incorrectly.
|
||||||
|
If steal not active: enters steal mode, advances turn.
|
||||||
|
If steal active: both teams failed, resets steal."""
|
||||||
|
current_question = game.get_current_question()
|
||||||
|
if not current_question:
|
||||||
|
return {'steal_active': False, 'both_failed': True}
|
||||||
|
|
||||||
|
# Increment question stats
|
||||||
|
current_question.times_incorrect += 1
|
||||||
|
|
||||||
|
if not game.steal_active:
|
||||||
|
# First wrong answer - enter steal mode, advance turn
|
||||||
|
game.steal_active = True
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
advance_turn(game, socketio_instance)
|
||||||
|
broadcast_steal_state(game, socketio_instance)
|
||||||
|
|
||||||
|
return {'steal_active': True, 'both_failed': False}
|
||||||
|
else:
|
||||||
|
# Second wrong answer - both teams failed
|
||||||
|
game.steal_active = False
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
broadcast_steal_state(game, socketio_instance)
|
||||||
|
|
||||||
|
return {'steal_active': False, 'both_failed': True}
|
||||||
|
|
||||||
|
|
||||||
|
def broadcast_steal_state(game, socketio_instance):
|
||||||
|
"""Broadcast steal state to all connected clients"""
|
||||||
|
steal_data = {
|
||||||
|
'steal_active': game.steal_active,
|
||||||
|
'current_turn_team_id': game.current_turn_team_id,
|
||||||
|
'current_turn_team_name': game.current_turn_team.name if game.current_turn_team else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
socketio_instance.emit('steal_state_changed', steal_data, room=f'game_{game.id}_contestant')
|
||||||
|
socketio_instance.emit('steal_state_changed', steal_data, room=f'game_{game.id}_admin')
|
||||||
|
|
||||||
|
|
||||||
def broadcast_turn_change(game, socketio_instance):
|
def broadcast_turn_change(game, socketio_instance):
|
||||||
"""Broadcast turn change to all connected clients"""
|
"""Broadcast turn change to all connected clients"""
|
||||||
ordered_teams = get_ordered_teams(game)
|
ordered_teams = get_ordered_teams(game)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default function GameAdminView() {
|
|||||||
const [newTeamName, setNewTeamName] = useState("");
|
const [newTeamName, setNewTeamName] = useState("");
|
||||||
const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null);
|
const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null);
|
||||||
const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null);
|
const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null);
|
||||||
|
const [stealActive, setStealActive] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadGameState();
|
loadGameState();
|
||||||
@@ -41,6 +42,7 @@ export default function GameAdminView() {
|
|||||||
setTotalQuestions(response.data.total_questions);
|
setTotalQuestions(response.data.total_questions);
|
||||||
setCurrentTurnTeamId(response.data.current_turn_team_id);
|
setCurrentTurnTeamId(response.data.current_turn_team_id);
|
||||||
setCurrentTurnTeamName(response.data.current_turn_team_name);
|
setCurrentTurnTeamName(response.data.current_turn_team_name);
|
||||||
|
setStealActive(response.data.steal_active || false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading game state:", error);
|
console.error("Error loading game state:", error);
|
||||||
}
|
}
|
||||||
@@ -87,6 +89,13 @@ export default function GameAdminView() {
|
|||||||
setCurrentTurnTeamName(data.current_turn_team_name);
|
setCurrentTurnTeamName(data.current_turn_team_name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("steal_state_changed", (data) => {
|
||||||
|
console.log("Steal state changed:", data);
|
||||||
|
setStealActive(data.steal_active);
|
||||||
|
setCurrentTurnTeamId(data.current_turn_team_id);
|
||||||
|
setCurrentTurnTeamName(data.current_turn_team_name);
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("question_with_answer");
|
socket.off("question_with_answer");
|
||||||
socket.off("score_updated");
|
socket.off("score_updated");
|
||||||
@@ -94,6 +103,7 @@ export default function GameAdminView() {
|
|||||||
socket.off("lifeline_updated");
|
socket.off("lifeline_updated");
|
||||||
socket.off("timer_reset");
|
socket.off("timer_reset");
|
||||||
socket.off("turn_changed");
|
socket.off("turn_changed");
|
||||||
|
socket.off("steal_state_changed");
|
||||||
};
|
};
|
||||||
}, [socket]);
|
}, [socket]);
|
||||||
|
|
||||||
@@ -220,6 +230,24 @@ export default function GameAdminView() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMarkCorrect = async () => {
|
||||||
|
try {
|
||||||
|
await adminAPI.markCorrect(gameId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error marking correct:", error);
|
||||||
|
alert("Error marking correct");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMarkWrong = async () => {
|
||||||
|
try {
|
||||||
|
await adminAPI.markWrong(gameId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error marking wrong:", error);
|
||||||
|
alert("Error marking wrong");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleAdvanceTurn = async () => {
|
const handleAdvanceTurn = async () => {
|
||||||
try {
|
try {
|
||||||
await adminAPI.advanceTurn(gameId);
|
await adminAPI.advanceTurn(gameId);
|
||||||
@@ -399,6 +427,20 @@ export default function GameAdminView() {
|
|||||||
<div className="answer-box">
|
<div className="answer-box">
|
||||||
<strong>Answer:</strong> {currentQuestion.answer}
|
<strong>Answer:</strong> {currentQuestion.answer}
|
||||||
</div>
|
</div>
|
||||||
|
{stealActive && (
|
||||||
|
<div style={{
|
||||||
|
marginTop: "0.75rem",
|
||||||
|
padding: "0.5rem 1rem",
|
||||||
|
background: "#FFF3E0",
|
||||||
|
border: "2px solid #FF9800",
|
||||||
|
borderRadius: "8px",
|
||||||
|
color: "#E65100",
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
}}>
|
||||||
|
STEAL OPPORTUNITY — {currentTurnTeamName}'s turn to steal
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p>No question selected. Click "Start Game" to begin.</p>
|
<p>No question selected. Click "Start Game" to begin.</p>
|
||||||
@@ -559,6 +601,7 @@ export default function GameAdminView() {
|
|||||||
{/* Button grid - 2 columns on desktop, 1 on mobile */}
|
{/* Button grid - 2 columns on desktop, 1 on mobile */}
|
||||||
<div className="controls-button-grid">
|
<div className="controls-button-grid">
|
||||||
{gameState?.is_active && teams.length > 0 && (
|
{gameState?.is_active && teams.length > 0 && (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={handleAdvanceTurn}
|
onClick={handleAdvanceTurn}
|
||||||
className="btn-next-turn"
|
className="btn-next-turn"
|
||||||
@@ -574,6 +617,41 @@ export default function GameAdminView() {
|
|||||||
>
|
>
|
||||||
Next Turn {currentTurnTeamName ? `(${currentTurnTeamName})` : ""}
|
Next Turn {currentTurnTeamName ? `(${currentTurnTeamName})` : ""}
|
||||||
</button>
|
</button>
|
||||||
|
{currentQuestion && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleMarkCorrect}
|
||||||
|
style={{
|
||||||
|
padding: "0.75rem 1.5rem",
|
||||||
|
background: "#4CAF50",
|
||||||
|
color: "white",
|
||||||
|
border: stealActive ? "3px solid #FF9800" : "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "1rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stealActive ? "Steal Correct (+1)" : "Correct (+1)"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleMarkWrong}
|
||||||
|
style={{
|
||||||
|
padding: "0.75rem 1.5rem",
|
||||||
|
background: stealActive ? "#b71c1c" : "#f44336",
|
||||||
|
color: "white",
|
||||||
|
border: stealActive ? "3px solid #FF9800" : "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "1rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stealActive ? "Steal Wrong" : "Wrong"}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{currentQuestion && (
|
{currentQuestion && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ export const adminAPI = {
|
|||||||
addLifeline: (gameId: number, teamId: number) =>
|
addLifeline: (gameId: number, teamId: number) =>
|
||||||
api.post(`/admin/game/${gameId}/team/${teamId}/add-lifeline`),
|
api.post(`/admin/game/${gameId}/team/${teamId}/add-lifeline`),
|
||||||
advanceTurn: (id: number) => api.post(`/admin/game/${id}/advance-turn`),
|
advanceTurn: (id: number) => api.post(`/admin/game/${id}/advance-turn`),
|
||||||
|
markCorrect: (id: number) => api.post(`/admin/game/${id}/mark-correct`),
|
||||||
|
markWrong: (id: number) => api.post(`/admin/game/${id}/mark-wrong`),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Categories API
|
// Categories API
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ def run_migrations_online():
|
|||||||
conf_args = current_app.extensions['migrate'].configure_args
|
conf_args = current_app.extensions['migrate'].configure_args
|
||||||
if conf_args.get("process_revision_directives") is None:
|
if conf_args.get("process_revision_directives") is None:
|
||||||
conf_args["process_revision_directives"] = process_revision_directives
|
conf_args["process_revision_directives"] = process_revision_directives
|
||||||
|
conf_args.pop("render_as_batch", None)
|
||||||
|
|
||||||
connectable = get_engine()
|
connectable = get_engine()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"""Add question stats and game steal state
|
||||||
|
|
||||||
|
Revision ID: 7b4626ec6a70
|
||||||
|
Revises: 9a119272b516
|
||||||
|
Create Date: 2026-04-03 14:39:34.805437
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import sqlite
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '7b4626ec6a70'
|
||||||
|
down_revision = '9a119272b516'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('games', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('steal_active', sa.Boolean(), nullable=True))
|
||||||
|
batch_op.drop_column('is_steal_mode')
|
||||||
|
batch_op.drop_column('turn_order')
|
||||||
|
|
||||||
|
with op.batch_alter_table('questions', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('times_correct', sa.Integer(), nullable=False, server_default='0'))
|
||||||
|
batch_op.add_column(sa.Column('times_incorrect', sa.Integer(), nullable=False, server_default='0'))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('questions', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('times_incorrect')
|
||||||
|
batch_op.drop_column('times_correct')
|
||||||
|
|
||||||
|
with op.batch_alter_table('games', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('turn_order', sqlite.JSON(), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column('is_steal_mode', sa.BOOLEAN(), nullable=True))
|
||||||
|
batch_op.drop_column('steal_active')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user