From 182eebbc35083bffbe3cb83972b55d2463c61eb1 Mon Sep 17 00:00:00 2001 From: Burathar Date: Sun, 19 Jul 2020 00:09:08 +0200 Subject: [PATCH] fix issues that arise from updating to database model 1.4 --- app/auth/forms.py | 7 +- app/auth/routes.py | 16 +-- app/main/forms.py | 11 +- app/main/routes.py | 58 +++++---- app/models/__init__.py | 2 +- app/models/game.py | 14 +- app/models/game_player.py | 7 +- app/models/location.py | 4 +- app/models/user.py | 4 +- app/templates/add_player.html | 4 +- app/templates/auth/user.html | 7 + app/templates/game_bunny_dashboard.html | 4 +- app/templates/game_hunter_dashboard.html | 2 +- app/templates/game_owner_dashboard.html | 18 +-- .../{player.html => game_player.html} | 14 +- app/templates/index.html | 2 +- app/tests/test_models.py | 74 +++++------ database_dump.txt | 121 ++++++++++++++++++ ...ns.py => 1d844798c277_reset_migrations.py} | 50 ++++---- the_hunt.py | 32 ++--- 20 files changed, 296 insertions(+), 155 deletions(-) create mode 100644 app/templates/auth/user.html rename app/templates/{player.html => game_player.html} (84%) create mode 100644 database_dump.txt rename migrations/versions/{35d99c2732cb_reset_migrations.py => 1d844798c277_reset_migrations.py} (80%) diff --git a/app/auth/forms.py b/app/auth/forms.py index 8afe482..2c7d62b 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -1,8 +1,7 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField, BooleanField from wtforms.validators import DataRequired, EqualTo, ValidationError, Length -from pytz import timezone -from app.models import Player +from app.models import User class LoginForm(FlaskForm): @@ -19,6 +18,6 @@ class RegistrationForm(FlaskForm): submit = SubmitField('Register') def validate_username(self, username): - player = Player.query.filter_by(name=username.data).first() - if player is not None: + user = User.query.filter_by(name=username.data).first() + if user is not None: raise ValidationError('Please use a different username.') \ No newline at end of file diff --git a/app/auth/routes.py b/app/auth/routes.py index fa5b123..04c8a10 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -2,7 +2,7 @@ from flask import render_template, flash, redirect, url_for from flask_login import login_user, logout_user, current_user, login_required from app import db from app.auth import bp -from app.models import Player +from app.models import User from app.auth.forms import LoginForm, RegistrationForm @bp.route('/login', methods=['GET', 'POST']) @@ -11,11 +11,11 @@ def login(): return redirect(url_for('main.index')) form = LoginForm() if form.validate_on_submit(): - player = Player.query.filter_by(name=form.username.data).first() - if player is None or not player.check_password(form.password.data): + user = User.query.filter_by(name=form.username.data).first() + if user is None or not user.check_password(form.password.data): flash('Invalid username or password') return redirect(url_for('auth.login')) - login_user(player, remember=form.remember_me.data) + login_user(user, remember=form.remember_me.data) return redirect(url_for('main.index')) return render_template('auth/login.html', title='Sign In', form=form) @@ -31,10 +31,10 @@ def register(): return redirect(url_for('main.index')) form = RegistrationForm() if form.validate_on_submit(): - player = Player(name=form.username.data) - player.set_password(form.password.data) - player.set_auth_hash() - db.session.add(player) + user = User(name=form.username.data) + user.set_password(form.password.data) + user.set_auth_hash() + db.session.add(user) db.session.commit() flash('Congratulations, you are now a registered user!') return redirect(url_for('auth.login')) diff --git a/app/main/forms.py b/app/main/forms.py index 2718e18..da0ab17 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -34,7 +34,8 @@ class ObjectiveForm(FlaskForm): submit = SubmitField('Save') def validate_objective_name(self, objective_name): - if objective_name.data == '': return + if objective_name.data == '': + return objective = Objective.query.filter_by(name=objective_name.data).first() if objective is not None: raise ValidationError('Please use a different name.') @@ -44,11 +45,11 @@ class PlayerUpdateForm(FlaskForm): submit = SubmitField('Update') class PlayerAddForm(FlaskForm): - name = StringField('Player Name', validators=[DataRequired(), Length(min=0, max=64)]) + name = StringField('Username', validators=[DataRequired(), Length(min=0, max=64)]) role = SelectField('Player Role', choices=[('none', 'none'), ('owner', 'owner'), ('hunter', 'hunter'), ('bunny', 'bunny')], validators=[DataRequired()]) submit_add = SubmitField('Create') -class PlayerCreateForm(FlaskForm): - name = StringField('Player Name', validators=[DataRequired(), Length(min=0, max=64)]) +class UserCreateForm(FlaskForm): + name = StringField('Username', validators=[DataRequired(), Length(min=0, max=64)]) role = SelectField('Player Role', choices=[('none', 'none'), ('owner', 'owner'), ('hunter', 'hunter'), ('bunny', 'bunny')], validators=[DataRequired()]) - submit_create = SubmitField('Create') \ No newline at end of file + submit_create = SubmitField('Create') diff --git a/app/main/routes.py b/app/main/routes.py index 16cf60a..f2ade1c 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -6,8 +6,8 @@ from sqlalchemy import and_ from io import BytesIO from app import db from app.main import bp -from app.models import Player, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder -from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, PlayerCreateForm, PlayerUpdateForm +from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder +from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm @bp.route('/') @bp.route('/index') @@ -24,7 +24,7 @@ def create_game(): form = CreateGameForm() if form.validate_on_submit(): game = Game(name=form.game_name.data, start_time=form.start_time.data, end_time=form.end_time.data) - game.game_players.append(GamePlayer(player=current_user, role=Role['owner'])) + game.game_players.append(GamePlayer(game_player=current_user, role=Role['owner'])) db.session.add(game) db.session.commit() flash(f"'{game.name}' had been created!") @@ -51,6 +51,7 @@ def game_dashboard(game_name): if role is None: abort(403) +@bp.route('/game//adduser', methods=['GET', 'POST']) @bp.route('/game//addplayer', methods=['GET', 'POST']) @login_required def add_player(game_name): @@ -58,62 +59,63 @@ def add_player(game_name): if not game.owned_by(current_user): abort(403) form_add = PlayerAddForm() - form_create = PlayerCreateForm() + form_create = UserCreateForm() if form_add.submit_add.data and form_add.validate_on_submit(): - player = Player.query.filter_by(form_add.name.data).first_or_404() - game.game_players.append(GamePlayer(player=player, role=Role[form_create.role.data])) + user = User.query.filter_by(form_add.name.data).first_or_404() + game.game_players.append(GamePlayer(user=user, role=Role[form_create.role.data])) return redirect(url_for('main.game_dashboard', game_name=game.name)) if form_create.submit_create.data and form_create.validate_on_submit(): - player = Player(name=form_create.name.data) - player.set_auth_hash() - game.game_players.append(GamePlayer(player=player, role=Role[form_create.role.data])) + user = User(name=form_create.name.data) + user.set_auth_hash() + game.game_players.append(GamePlayer(user=user, role=Role[form_create.role.data])) db.session.commit() return redirect(url_for('main.game_dashboard', game_name=game.name)) - return render_template('add_player.html', title=f'Add Player for {game_name}', form_add=form_add, form_create=form_create, game=game) + return render_template('add_player.html', title=f'Add User for {game_name}', form_add=form_add, form_create=form_create, game=game) -@bp.route('/game//removeplayer/') +@bp.route('/game//removeuser/') +@bp.route('/game//removeplayer/') @login_required -def remove_player(game_name, player_name): +def remove_player(game_name, username): game = Game.query.filter_by(name=game_name).first_or_404() if not game.owned_by(current_user): abort(403) - player = Player.query.filter(and_(Player.name == player_name, Player.games.contains(game))).first_or_404() - game.players.remove(player) + user = User.query.filter(and_(User.name == username, User.games.contains(game))).first_or_404() + game.players.remove(user) db.session.commit() return redirect(url_for('main.game_dashboard', game_name=game.name)) -@bp.route('/game//player/', methods=['GET', 'POST']) +@bp.route('/game//player/', methods=['GET', 'POST']) @login_required -def game_player(game_name, player_name): +def game_player(game_name, username): game = Game.query.filter_by(name=game_name).first_or_404() - if not game.owned_by(current_user): + if not game.owned_by(current_user): abort(403) - player = Player.query.filter(and_(Player.name == player_name, Player.games.contains(game))).first_or_404() - gameplayer = [gameplayer for gameplayer in player.player_games if gameplayer.game == game][0] + user = User.query.filter(and_(User.name == username, User.games.contains(game))).first_or_404() + gameplayer = [gameplayer for gameplayer in user.user_games if gameplayer.game == game][0] form = PlayerUpdateForm(role=gameplayer.role.name) if form.validate_on_submit(): gameplayer.role = Role[form.role.data] db.session.commit() return redirect(url_for('main.game_dashboard', game_name=game.name)) - return render_template('player.html', title=f'{player.name} in {game_name}', game=game, player=player, form=form, json=json, location_encoder=LocationEncoder) + return render_template('game_player.html', title=f'{user.name} in {game_name}', game=game, user=user, form=form, json=json, location_encoder=LocationEncoder) -@bp.route('/player//qrcode.png') +@bp.route('/user//qrcode.png') @login_required -def player_qrcode(auth_hash): - player = Player.query.filter_by(auth_hash=auth_hash).first_or_404() - if not current_user.owns_game_played_by(player): +def user_qrcode(auth_hash): + user = User.query.filter_by(auth_hash=auth_hash).first_or_404() + if not current_user.owns_game_played_by(user): abort(403) img = generate_qr_code(url_for('main.player', auth_hash=auth_hash, _external=True)) return serve_pil_image(img) -@bp.route('/player/') +@bp.route('/user/') @login_required -def player(auth_hash): - player = Player.query.filter_by(auth_hash=auth_hash).first_or_404() - return render_template('player.html',title=f'Player: {player.name}', player=player) +def user(auth_hash): + user = User.query.filter_by(auth_hash=auth_hash).first_or_404() + return render_template('auth/user.html', title=f'User: {user.name}', user=user) @bp.route('/game//add_objective', methods=['GET', 'POST']) @login_required diff --git a/app/models/__init__.py b/app/models/__init__.py index 4dc5393..ae18358 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -1,8 +1,8 @@ - from .game import Game from .game_player import GamePlayer from .game_state import GameState from .location import Location, LocationEncoder +from .notification import Notification from .notification_player import NotificationPlayer from .objective import Objective, ObjectiveMinimalEncoder from .player_caught_player import PlayerCaughtPlayer diff --git a/app/models/game.py b/app/models/game.py index a22c088..21d9d9b 100644 --- a/app/models/game.py +++ b/app/models/game.py @@ -15,8 +15,10 @@ class Game(db.Model): 'GamePlayer', back_populates='game', cascade="save-update, merge, delete, delete-orphan") + players = association_proxy('game_players', 'user', + creator=lambda user: GamePlayer(user=user)) users = association_proxy('game_players', 'user', - creator=lambda user: GamePlayer(user=user)) # to enable game.players.append(player) + creator=lambda user: GamePlayer(user=user)) objectives = db.relationship( 'Objective', lazy='select', @@ -27,11 +29,11 @@ class Game(db.Model): backref=db.backref('game', lazy='joined')) def last_player_locations(self): - return [player.last_location(self) for player in self.users if player.locations] + return [user.last_location(self) for user in self.players if user.locations] def bunnies(self): # pylint: disable=not-an-iterable - return [gameplayer.player for gameplayer in self.game_players if gameplayer.role == Role.bunny] + return [gameplayer.user for gameplayer in self.game_players if gameplayer.role == Role.bunny] def last_locations(self, players): locations = [] @@ -39,7 +41,7 @@ class Game(db.Model): locations.append(player.last_location(self)) return locations - def owned_by(self, player): + def owned_by(self, user): # pylint: disable=not-an-iterable - '''given player is an owner of game''' - return player in [gameplayer.player for gameplayer in self.game_players if gameplayer.role == Role.owner] + '''given user is an owner of this game''' + return user in [gameplayer.user for gameplayer in self.game_players if gameplayer.role == Role.owner] diff --git a/app/models/game_player.py b/app/models/game_player.py index 7b45910..7987e86 100644 --- a/app/models/game_player.py +++ b/app/models/game_player.py @@ -1,4 +1,5 @@ from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.schema import UniqueConstraint from app import db from .role import Role @@ -9,11 +10,13 @@ from .player_caught_player import PlayerCaughtPlayer class GamePlayer(db.Model): __tablename__ = 'game_player' id = db.Column(db.Integer, primary_key=True) - game_id = db.Column(db.Integer, db.ForeignKey('game.id'), primary_key=True, nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True, nullable=False) + game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) role = db.Column(db.Enum(Role), server_default=Role(0).name, nullable=False) game = db.relationship('Game', back_populates='game_players') user = db.relationship('User', back_populates='user_games') + __table_args__ = (UniqueConstraint('game_id', 'user_id', name='_game_user_uc'), + ) player_notifications = db.relationship( 'NotificationPlayer', diff --git a/app/models/location.py b/app/models/location.py index 999321f..31bfde6 100644 --- a/app/models/location.py +++ b/app/models/location.py @@ -6,7 +6,7 @@ from app import db class Location(db.Model): __tablename__ = 'location' id = db.Column(db.Integer, primary_key=True) - player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) longitude = db.Column(db.Numeric(precision=15, scale=10, asdecimal=False, decimal_return_scale=None), nullable=False) # maybe check asdecimal and decimal_return_scale later? latitude = db.Column(db.Numeric(precision=15, scale=10, asdecimal=False, decimal_return_scale=None), nullable=False) timestamp = db.Column(db.DateTime, server_default=func.now(), nullable=False) @@ -17,7 +17,7 @@ class Location(db.Model): class LocationEncoder(JSONEncoder): def default(self, location): return { - 'player_name' : location.player.name, + 'username' : location.user.name, 'longitude' : location.longitude, 'latitude' : location.latitude, 'timestamp_utc' : str(location.timestamp) diff --git a/app/models/user.py b/app/models/user.py index 039ad05..9d15930 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -10,7 +10,7 @@ from app.models import GamePlayer, Role class User(UserMixin, db.Model): """ !Always call set_auth_hash() after creating new instance! """ - __tablename__ = 'player' + __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True, nullable=False) auth_hash = db.Column(db.String(32), unique=True, nullable=True) @@ -36,6 +36,8 @@ class User(UserMixin, db.Model): self.auth_hash = token_hex(16) def check_password(self, password): + if not password or not self.password_hash: + return False return check_password_hash(self.password_hash, password) def locations_during_game(self, game): diff --git a/app/templates/add_player.html b/app/templates/add_player.html index fe4122c..10eda80 100644 --- a/app/templates/add_player.html +++ b/app/templates/add_player.html @@ -3,7 +3,7 @@ {% block app_content %}

Add Player To Game

-

Add Existing Player

+

Add Existing User

@@ -14,7 +14,7 @@
-

Create new Player

+

Create new User

diff --git a/app/templates/auth/user.html b/app/templates/auth/user.html new file mode 100644 index 0000000..5b936dd --- /dev/null +++ b/app/templates/auth/user.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +

User

+ This page is is progress, it should enable you to claim a player account using the authhash, as long as the player hasnt logged in yet. +{% endblock %} \ No newline at end of file diff --git a/app/templates/game_bunny_dashboard.html b/app/templates/game_bunny_dashboard.html index a674330..89c7cf4 100644 --- a/app/templates/game_bunny_dashboard.html +++ b/app/templates/game_bunny_dashboard.html @@ -28,7 +28,7 @@ {% for player in game.players %} {{ player.name }} - {% for gameplayer in player.player_games if gameplayer.game == game %} + {% for gameplayer in player.user_games if gameplayer.game == game %} {{ gameplayer.role.name }} {% endfor %} {{ player.found_objectives | selectattr('game', '==', game)|list|length}} @@ -129,7 +129,7 @@ ], {icon: greenIcon}).addTo(map); var timestamp_utc = moment.utc(players[i]['timestamp_utc']).toDate() var timestamp_local = moment(timestamp_utc).local().format('YYYY-MM-DD HH:mm'); - playerMarker.bindTooltip(`${players[i]['player_name']}
+ playerMarker.bindTooltip(`${players[i]['username']}
${timestamp_local}`).openPopup(); } diff --git a/app/templates/game_hunter_dashboard.html b/app/templates/game_hunter_dashboard.html index a9237ae..84eb43a 100644 --- a/app/templates/game_hunter_dashboard.html +++ b/app/templates/game_hunter_dashboard.html @@ -72,7 +72,7 @@ ], {icon: greenIcon}).addTo(map); var timestamp_utc = moment.utc(players[i]['timestamp_utc']).toDate() var timestamp_local = moment(timestamp_utc).local().format('YYYY-MM-DD HH:mm'); - playerMarker.bindTooltip(`${players[i]['player_name']}
+ playerMarker.bindTooltip(`${players[i]['username']}
${timestamp_local}`).openPopup(); } diff --git a/app/templates/game_owner_dashboard.html b/app/templates/game_owner_dashboard.html index a674330..805362c 100644 --- a/app/templates/game_owner_dashboard.html +++ b/app/templates/game_owner_dashboard.html @@ -25,20 +25,20 @@ - {% for player in game.players %} + {% for user in game.users %} - {{ player.name }} - {% for gameplayer in player.player_games if gameplayer.game == game %} + {{ user.name }} + {% for gameplayer in user.user_games if gameplayer.game == game %} {{ gameplayer.role.name }} {% endfor %} - {{ player.found_objectives | selectattr('game', '==', game)|list|length}} - {{ player.caught_players | selectattr('game', '==', game)|list|length}} - {{ player.caught_by_players | selectattr('game', '==', game)|list|length}} - {% with location = player.last_location(game) %} + {{ user.found_objectives | selectattr('game', '==', game)|list|length}} + {{ user.caught_players | selectattr('game', '==', game)|list|length}} + {{ user.caught_by_players | selectattr('game', '==', game)|list|length}} + {% with location = user.last_location(game) %} {% if location %}{{ moment(location.timestamp).fromNow()}}: {% endif %} {{ location }} {% endwith %} - + @@ -129,7 +129,7 @@ ], {icon: greenIcon}).addTo(map); var timestamp_utc = moment.utc(players[i]['timestamp_utc']).toDate() var timestamp_local = moment(timestamp_utc).local().format('YYYY-MM-DD HH:mm'); - playerMarker.bindTooltip(`${players[i]['player_name']}
+ playerMarker.bindTooltip(`${players[i]['username']}
${timestamp_local}`).openPopup(); } diff --git a/app/templates/player.html b/app/templates/game_player.html similarity index 84% rename from app/templates/player.html rename to app/templates/game_player.html index 45d1db4..9d5205b 100644 --- a/app/templates/player.html +++ b/app/templates/game_player.html @@ -8,22 +8,22 @@ {% endblock %} {% block app_content %} -

Player: {{ player.name }}

+

Player: {{ user.name }}


{{ form.hidden_tag() }} - {% for gameplayer in player.player_games if gameplayer.game == game %} + {% for gameplayer in user.user_games if gameplayer.game == game %} {{ wtf.form_field(form.role, class='form-control') }} {% endfor %} {{ wtf.form_field(form.submit, class='btn btn-primary', value='Update') }}
- {% if player.auth_hash %} + {% if user.auth_hash %}
- qr_code_failed + qr_code_failed
{% endif %}
@@ -37,7 +37,7 @@ {{ moment.include_moment() }}