diff --git a/app/main/routes.py b/app/main/routes.py index 5ee120f..b4918b9 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -13,6 +13,8 @@ from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, PlayerC @bp.route('/index') @login_required def index(): + if len(current_user.games) == 1: + return redirect(url_for('main.game_dashboard', game_name=current_user.games[0].name)) return render_template("index.html", title='Home') @bp.route('/create_game', methods=['GET', 'POST']) @@ -32,17 +34,28 @@ def create_game(): @bp.route('/game//dashboard') @login_required def game_dashboard(game_name): - #game = Game.query.filter(Game.game_players.any(and_(GamePlayer.player.has(Player.name == current_user.name), GamePlayer.role == 'owner'))).first_or_404() game = Game.query.filter_by(name=game_name).first_or_404() - if not is_game_owner(game): + role = current_user.get_role_for_game(game) + if role == Role.owner: + return render_template('game_owner_dashboard.html', title='Game Dashboard', + game=game, json=json, objective_encoder=ObjectiveMinimalEncoder, location_encoder=LocationEncoder) + if role == Role.bunny: + return render_template('game_bunny_dashboard.html', title='Game Dashboard', + game=game, json=json, objective_encoder=ObjectiveMinimalEncoder, location_encoder=LocationEncoder) + if role == Role.hunter: + return render_template('game_hunter_dashboard.html', title='Game Dashboard', + game=game, json=json, location_encoder=LocationEncoder) + if role == Role.none: + return render_template('game_hunter_dashboard.html', title='Game Dashboard', + game=game, json=json, location_encoder=LocationEncoder) + if role is None: abort(403) - return render_template('game_dashboard.html', title='Game Dashboard', game=game, json=json, objective_encoder=ObjectiveMinimalEncoder, location_encoder=LocationEncoder) @bp.route('/game//addplayer', methods=['GET', 'POST']) @login_required def add_player(game_name): game = Game.query.filter_by(name=game_name).first_or_404() - if not is_game_owner(game): + if not game.owned_by(current_user): abort(403) form_add = PlayerAddForm() form_create = PlayerCreateForm() @@ -65,7 +78,7 @@ def add_player(game_name): @login_required def remove_player(game_name, player_name): game = Game.query.filter_by(name=game_name).first_or_404() - if not is_game_owner(game): + 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) @@ -76,7 +89,7 @@ def remove_player(game_name, player_name): @login_required def game_player(game_name, player_name): game = Game.query.filter_by(name=game_name).first_or_404() - if not is_game_owner(game): + 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] @@ -91,7 +104,7 @@ def game_player(game_name, player_name): @login_required def player_qrcode(auth_hash): player = Player.query.filter_by(auth_hash=auth_hash).first_or_404() - if not is_player_game_owner(player): + if not current_user.owns_game_played_by(player): abort(403) img = generate_qr_code(url_for('main.player', auth_hash=auth_hash, _external=True)) return serve_pil_image(img) @@ -102,25 +115,11 @@ 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) -'''given player is an owner of the given game''' -def is_game_owner(game, owner=current_user): - return owner in [gameplayer.player for gameplayer in game.game_players if gameplayer.role == Role.owner] - -'''given player is an owner of a game the subject_player participates in''' -def is_player_game_owner(subject_player, owner=current_user): - return owner in [gameplayer.player for gameplayers in - [game.game_players for game in subject_player.games] - for gameplayer in gameplayers if gameplayer.role == Role.owner] - -'''given player is an owner of a game the given object is part of''' -def is_objective_owner(objective, owner=current_user): - return owner in [gameplayer.player for gameplayer in objective.game.game_players if gameplayer.role == Role.owner] - @bp.route('/game//add_objective', methods=['GET', 'POST']) @login_required def add_objective(game_name): game = Game.query.filter_by(name=game_name).first_or_404() - if not is_game_owner(game): + if not game.owned_by(current_user): abort(403) form = ObjectiveForm() objective = Objective(name='', latitude=52.0932, longitude=5.12405) @@ -137,9 +136,9 @@ def add_objective(game_name): @login_required def delete_objective(objective_hash): objective = Objective.query.filter_by(hash=objective_hash).first_or_404() - if not is_objective_owner(objective): + if not objective.owned_by(current_user): abort(403) - if is_objective_owner(objective): + else: db.session.delete(objective) db.session.commit() return redirect(url_for('main.game_dashboard', game_name=objective.game.name)) @@ -166,7 +165,7 @@ def serve_pil_image(pil_img): @login_required def objective_qrcode(objective_hash): objective = Objective.query.filter_by(hash=objective_hash).first_or_404() - if not is_objective_owner(objective): + if not objective.owned_by(current_user): abort(403) img = generate_qr_code(url_for('main.objective', objective_hash=objective.hash, _external=True)) return serve_pil_image(img) @@ -175,7 +174,7 @@ def objective_qrcode(objective_hash): @login_required def objective(objective_hash): objective = Objective.query.filter_by(hash=objective_hash).first_or_404() - owner = is_objective_owner(objective) + owner = objective.owned_by(current_user) qrcode = generate_qr_code(objective) if owner else None form = ObjectiveForm() if form.submit.data and form.validate() and owner: diff --git a/app/models.py b/app/models.py index 72514fd..25d738b 100644 --- a/app/models.py +++ b/app/models.py @@ -83,6 +83,20 @@ class Game(db.Model): def last_player_locations(self): return [player.last_location(self) for player in self.players if player.locations] + def bunnies(self): + # pylint: disable=not-an-iterable + return [gameplayer.player for gameplayer in self.game_players if gameplayer.role == Role.bunny] + + def last_locations(self, players): + locations = [] + for player in players: + locations.append(player.last_location(self)) + return locations + +def owned_by(self, player): + '''given player is an owner of game''' + return player in [gameplayer.player for gameplayer in self.game_players if gameplayer.role == Role.owner] + class Player(UserMixin, db.Model): """ !Always call set_auth_hash() after creating new instance! """ __tablename__ = 'player' @@ -142,7 +156,7 @@ class Player(UserMixin, db.Model): def check_password(self, password): return check_password_hash(self.password_hash, password) - def locations_game(self, game): + def locations_during_game(self, game): # pylint: disable=not-an-iterable if not self.locations: return None @@ -158,7 +172,21 @@ class Player(UserMixin, db.Model): return None if game is None: return max(self.locations, key=lambda location: location.timestamp) - return max(self.locations_game(game), key=lambda location: location.timestamp) + return max(self.locations_during_game(game), key=lambda location: location.timestamp) + + def role_in_game(self, game): + '''returns the role as Role enum of player in given game. Returns None if player does not participate in game''' + # pylint: disable=not-an-iterable + gameplayers = [gameplayer for gameplayer in self.player_games if gameplayer.game == game] + if not gameplayers: + return None + return gameplayers[0].role + +def owns_game_played_by(self, player): + '''self is an owner of a game the player participates in''' + return self in [gameplayer.player for gameplayers in + [game.game_players for game in player.games] + for gameplayer in gameplayers if gameplayer.role == Role.owner] @staticmethod def delete_orphans(): @@ -187,6 +215,10 @@ class Objective(db.Model): def set_hash(self): self.hash = token_hex(16) + + def owned_by(self, player): + '''given player is an owner of a game object is part of''' + return player in [gameplayer.player for gameplayer in self.game.game_players if gameplayer.role == Role.owner] class ObjectiveMinimalEncoder(JSONEncoder): def default(self, objective): diff --git a/app/tests/test_models.py b/app/tests/test_models.py new file mode 100644 index 0000000..d314002 --- /dev/null +++ b/app/tests/test_models.py @@ -0,0 +1,113 @@ +import unittest +from app import create_app, db +from app.models import Player, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder +from config import Config + +class TestConfig(Config): + TESTING = True + WTF_CSRF_ENABLED = False + DEBUG = False + SQLALCHEMY_DATABASE_URI = 'sqlite://' + +class ModelsCase(unittest.TestCase): + # implement this: https://stackoverflow.com/questions/47294304/how-to-mock-current-user-in-flask-templates + def setUp(self): + self.app = create_app(TestConfig) + self.app_context = self.app.app_context() + self.app_context.push() + db.create_all() + + def tearDown(self): + db.session.remove() + db.drop_all() + self.app_context.pop() + + def test_is_game_owner(self): + g1 = Game(name='TestGame') + p1 = Player(name='Henk') + p2 = Player(name='Alfred') + + g1.game_players.append(GamePlayer(player=p1, role=Role.owner)) + g1.game_players.append(GamePlayer(player=p2, role=Role.bunny)) + + db.session.add(g1) + db.session.commit() + + self.assertTrue(g1.is_game_owner(p1)) + self.assertFalse(g1.is_game_owner(p2)) + + def test_is_player_game_owner(self): + g1 = Game(name='TestGame') + g2 = Game(name='AnotherGame') + + p1 = Player(name='Henk') + p2 = Player(name='Alfred') + p3 = Player(name='Sasha') + + g1.game_players.append(GamePlayer(player=p1, role=Role.owner)) + g1.game_players.append(GamePlayer(player=p2, role=Role.bunny)) + + g2.game_players.append(GamePlayer(player=p1, role=Role.hunter)) + g2.game_players.append(GamePlayer(player=p3, role=Role.bunny)) + + + db.session.add(g1) + db.session.add(g2) + db.session.commit() + + self.assertTrue(p1.owns_game_played_by(player=p2), "owner owns subject_player's game") + self.assertFalse(p1.owns_game_played_by(player=p3), "owner doesn't own subject_player's game") + self.assertTrue(p1.owns_game_played_by(player=p1), "owner owns it own's game") + + + def test_is_objective_owner(self): + g1 = Game(name='TestGame') + g2 = Game(name='AnotherGame') + p1 = Player(name='Henk') + + g1.game_players.append(GamePlayer(player=p1, role=Role.owner)) + g2.game_players.append(GamePlayer(player=p1, role=Role.bunny)) + + o1 = Objective(name='o1') + o1.set_hash() + o2 = Objective(name='o2') + o2.set_hash() + + g1.objectives.append(o1) + g2.objectives.append(o2) + + db.session.add(g1) + db.session.add(g2) + db.session.commit() + + self.assertTrue(o1.owned_by(p1)) + self.assertFalse(o2.owned_by(p1)) + + def test_role_in_game(self): + g1 = Game(name='TestGame') + + p1 = Player(name='Henk') + p2 = Player(name='Alfred') + p3 = Player(name='Sasha') + p4 = Player(name='Demian') + p5 = Player(name='Karl') + + g1.game_players.append(GamePlayer(player=p1, role=Role.owner)) + g1.game_players.append(GamePlayer(player=p2, role=Role.bunny)) + g1.game_players.append(GamePlayer(player=p3, role=Role.hunter)) + g1.game_players.append(GamePlayer(player=p4, role=Role.none)) + + db.session.add(g1) + db.session.add(p5) + db.session.commit() + + self.assertEqual(p1.role_in_game(g1), Role.owner) + self.assertEqual(p2.role_in_game(g1), Role.bunny) + self.assertEqual(p3.role_in_game(g1), Role.hunter) + self.assertEqual(p4.role_in_game(g1), Role.none) + self.assertEqual(p5.role_in_game(g1), None) + with self.assertRaises(AttributeError): + g1.get_role_for_game(None) + +if __name__ == '__main__': + unittest.main(verbosity=2)