diff --git a/app/main/forms.py b/app/main/forms.py index dcb43d5..84b60c1 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -34,10 +34,13 @@ class ObjectiveForm(FlaskForm): latitude = FloatField('Latitude', validators=[DataRequired(), NumberRange(min=-90, max=90)]) longitude = FloatField('Longitude', validators=[DataRequired(), NumberRange(min=-180, max=180)]) submit = SubmitField('Save') + old_name = '' def validate_objective_name(self, objective_name): if objective_name.data == '': return + if objective_name.data == self.old_name: + return objective = Objective.query.filter_by(name=objective_name.data).first() if objective is not None: raise ValidationError('Please use a different name.') diff --git a/app/main/routes.py b/app/main/routes.py index bb56734..150e05b 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -13,7 +13,7 @@ from sqlalchemy import and_ from app import db from app.main import bp from app.utils import generate_qr_code, serve_pil_image -from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder, PlayerCaughtPlayer +from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder, PlayerCaughtPlayer, Review from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm, CatchBunnyForm @bp.before_app_request @@ -35,7 +35,8 @@ def index(): 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(name=form.game_name.data, start_time=form.start_time.data, + end_time=form.end_time.data) game.players.append(GamePlayer(user=current_user, role=Role['owner'])) db.session.add(game) db.session.commit() @@ -63,17 +64,18 @@ def game_dashboard(game_name): game = Game.query.filter_by(name=game_name).first_or_404() role = current_user.role_in_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) + 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, location_encoder=LocationEncoder) + return render_template('game_bunny_dashboard.html', title='Game Dashboard', game=game, + json=json, location_encoder=LocationEncoder) if role == Role.hunter: - return render_template('game_hunter_dashboard.html', title='Game Dashboard', - game=game, json=json, location_encoder=LocationEncoder) + 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) + return render_template('game_hunter_dashboard.html', title='Game Dashboard', game=game, + json=json, location_encoder=LocationEncoder) if role is None: abort(403) @@ -152,6 +154,20 @@ def get_caught_bunny_photo_directory(game): secure_filename(game.name) / \ current_app.config['PLAYER_CAUGHT_PLAYER_PHOTO_DIR_NAME'] +@bp.route('/game//review') +@login_required +def review_caught_bunny_photos(game_name): + game = Game.query.filter_by(name=game_name).first_or_404() + if not game.owned_by(current_user): + abort(403) + pcp_id = request.args.get('pcp_id', default=-1, type=int) + action = request.args.get('action', default='none', type=str).lower() + if pcp_id != -1: + pcp = PlayerCaughtPlayer.query.filter_by(id=pcp_id).first_or_404() + review = Review.parse_string(action) + pcp.review = review + db.session.commit() + return render_template('review_caught_bunny_photos.html', game=game) @bp.route('/game//adduser', methods=['GET', 'POST']) @bp.route('/game//addplayer', methods=['GET', 'POST']) @@ -176,7 +192,8 @@ def add_player(game_name): db.session.commit() return redirect(url_for('main.game_dashboard', game_name=game.name)) - return render_template('add_player.html', title=f'Add User 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//removeuser/') @bp.route('/game//removeplayer/') @@ -229,7 +246,8 @@ def add_objective(game_name): db.session.commit() flash(f"Objective has been added!") return redirect(url_for('main.game_dashboard', game_name=game.name)) - return render_template('objective.html', title=f'Add Objective for {game_name}', form=form, objective=objective, owner=True) + return render_template('objective.html', title=f'Add Objective for {game_name}', + form=form, objective=objective, owner=True) @bp.route('/objective//delete', methods=['GET']) @login_required @@ -269,17 +287,18 @@ def objective(objective_hash): else: flash('You have already found this objective') return redirect(url_for('main.game_dashboard', game_name=objective.game.name)) - elif not objective.owned_by(current_user): + if not objective.owned_by(current_user): flash("Only bunnies in an objective's game can find objectives!") abort(403) - owner = objective.owned_by(current_user) - qrcode = generate_qr_code(objective) if owner else None + qrcode = generate_qr_code(objective) form = ObjectiveForm() - if form.submit.data and form.validate() and owner: + form.old_name = objective.name + if form.submit.data and form.validate(): objective.name = form.objective_name.data objective.longitude = form.longitude.data objective.latitude = form.latitude.data db.session.commit() return redirect(url_for('main.game_dashboard', game_name=objective.game.name)) - return render_template('objective.html', title='Objective view', objective=objective, owner=owner, form=form, qrcode=qrcode) + return render_template('objective.html', title='Objective view', + objective=objective, owner=True, form=form, qrcode=qrcode) diff --git a/app/models/__init__.py b/app/models/__init__.py index ae18358..a2a5802 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -7,5 +7,6 @@ from .notification_player import NotificationPlayer from .objective import Objective, ObjectiveMinimalEncoder from .player_caught_player import PlayerCaughtPlayer from .player_found_objective import PlayerFoundObjective +from .review import Review from .role import Role from .user import User diff --git a/app/models/game.py b/app/models/game.py index d7ff218..9324b16 100644 --- a/app/models/game.py +++ b/app/models/game.py @@ -3,6 +3,7 @@ from app import db from .game_state import GameState from .game_player import GamePlayer from .role import Role +from .review import Review class Game(db.Model): __tablename__ = 'game' @@ -47,6 +48,12 @@ class Game(db.Model): return [gameplayer for gameplayer in self.players if gameplayer.role == Role.bunny] def owned_by(self, user): - # pylint: disable=not-an-iterable '''given user is an owner of this game''' + # pylint: disable=not-an-iterable return user in [gameplayer.user for gameplayer in self.players if gameplayer.role == Role.owner] + + def unreviewed_bunny_photos(self): + # pylint: disable=not-an-iterable + return [pcp for pcps in + [player.player_caught_players for player in self.players] + for pcp in pcps if pcp.review == Review.none] diff --git a/app/models/game_player.py b/app/models/game_player.py index 6814e25..f156c85 100644 --- a/app/models/game_player.py +++ b/app/models/game_player.py @@ -7,6 +7,7 @@ from app import db from .role import Role from .notification_player import NotificationPlayer from .player_found_objective import PlayerFoundObjective +from .review import Review class GamePlayer(db.Model): __tablename__ = 'game_player' @@ -49,10 +50,20 @@ class GamePlayer(db.Model): objectives = ['['] for objective in self.game.objectives: obj = { - 'name' : objective.name, - 'longitude' : objective.longitude, - 'latitude' : objective.latitude, - 'found' : objective in self.found_objectives} + 'name' : objective.name, + 'longitude' : objective.longitude, + 'latitude' : objective.latitude, + 'found' : objective in self.found_objectives} objectives.append(json.dumps(obj)) objectives.append(',') return ''.join(objectives)[:-1] + ']' + + def accepted_caught_players(self): + return [pcp.caught_player + for pcp in self.player_caught_players + if pcp.review == Review.accepted] + + def accepted_caught_by_players(self): + return [pcp.catching_player + for pcp in self.player_caught_by_players + if pcp.review == Review.accepted] diff --git a/app/models/player_caught_player.py b/app/models/player_caught_player.py index 9e9fb8a..7224e1e 100644 --- a/app/models/player_caught_player.py +++ b/app/models/player_caught_player.py @@ -2,6 +2,7 @@ from sqlalchemy.sql import func from app import db from .game_player import GamePlayer +from .review import Review class PlayerCaughtPlayer(db.Model): __tablename__ = 'player_caught_player' @@ -9,6 +10,7 @@ class PlayerCaughtPlayer(db.Model): catching_player_id = db.Column(db.Integer, db.ForeignKey('game_player.id'), nullable=False) caught_player_id = db.Column(db.Integer, db.ForeignKey('game_player.id'), nullable=False) timestamp = db.Column(db.DateTime, server_default=func.now(), nullable=False) + review = db.Column(db.Enum(Review), server_default=Review(0).name, nullable=False) catching_player = db.relationship('GamePlayer', primaryjoin=(catching_player_id == GamePlayer.id), backref=db.backref('player_caught_players', cascade='save-update, merge, delete, delete-orphan')) caught_player = db.relationship('GamePlayer', primaryjoin=(caught_player_id == GamePlayer.id), @@ -23,4 +25,4 @@ pc = PlayerCaughtPlayer(caught_player=p2, catching_player=p1) db.session.add(pc) db.session.commit() -''' \ No newline at end of file +''' diff --git a/app/models/review.py b/app/models/review.py new file mode 100644 index 0000000..eb35f8f --- /dev/null +++ b/app/models/review.py @@ -0,0 +1,14 @@ +from enum import Enum + +class Review(Enum): + none = 0 + denied = 1 + accepted = 2 + + @classmethod + def parse_string(cls, string): + if string == 'accept' or string == 'accepted': + return cls.accepted + if string == 'deny' or string == 'denied': + return cls.denied + return cls.none \ No newline at end of file diff --git a/app/templates/_game_player_info.html b/app/templates/_game_player_info.html index cba60d2..c1d6c65 100644 --- a/app/templates/_game_player_info.html +++ b/app/templates/_game_player_info.html @@ -17,6 +17,5 @@ End Time {% if game.end_time %}{{ moment(game.end_time).format('DD-MM-YYYY, hh:mm')}}{% else %}-{% endif %} - \ No newline at end of file diff --git a/app/templates/_review_photo.html b/app/templates/_review_photo.html new file mode 100644 index 0000000..543ac68 --- /dev/null +++ b/app/templates/_review_photo.html @@ -0,0 +1,37 @@ +
+
+
+
+ + + + + + + + + + + + + +
Hunter{{ pcp.catching_player.user.name }}
Bunny{{ pcp.caught_player.user.name }}
Time{{ pcp.timestamp.strftime('%Y-%m-%d %H:%M') }}
+
+
+ +
+
+ could not load photo +
+
diff --git a/app/templates/game_hunter_dashboard.html b/app/templates/game_hunter_dashboard.html index bd19c18..e653533 100644 --- a/app/templates/game_hunter_dashboard.html +++ b/app/templates/game_hunter_dashboard.html @@ -19,16 +19,24 @@ Player Name - Times Caught + Times Caught + + (Accepted/Denied/Not reviewed) + + Last location + {% set player = current_user.player_in(game) %} {% for bunny in game.bunnies() %} {{ bunny.user.name }} - {{ bunny.player_caught_by_players | selectattr('catching_player', '==', current_user.player_in(game)) |list|length}} + {{ bunny.player_caught_by_players | selectattr('catching_player', '==', player) | selectattr('review.name', '==', 'accepted') |list|length}} / + {{ bunny.player_caught_by_players | selectattr('catching_player', '==', player) | selectattr('review.name', '==', 'denied') |list|length}} / + {{ bunny.player_caught_by_players | selectattr('catching_player', '==', player) | selectattr('review.name', '==', 'none') |list|length}} + {% with location = bunny.last_location() %} {% if location %}{{ moment(location.timestamp).fromNow()}}: {% endif %} {{ location }} diff --git a/app/templates/game_owner_dashboard.html b/app/templates/game_owner_dashboard.html index dc0e6ce..9a61ab2 100644 --- a/app/templates/game_owner_dashboard.html +++ b/app/templates/game_owner_dashboard.html @@ -9,7 +9,11 @@ {% block app_content %}

{{ game.name }} Dashboard

- +{% if game.unreviewed_bunny_photos() %} + + + +{% endif %}

Players:

Add player

@@ -31,8 +35,8 @@ {{ player.user.name }} {{ player.role.name }} {{ player.found_objectives | list | length }} - {{ player.caught_players | list | length }} - {{ player.caught_by_players | list | length }} + {{ player.accepted_caught_players() | list | length }} + {{ player.accepted_caught_by_players() | list | length }} {% with location = player.last_location() %} {% if location %}{{ moment(location.timestamp).fromNow()}}: {% endif %} {{ location }} diff --git a/app/templates/game_player.html b/app/templates/game_player.html index ae12f28..eb2ec19 100644 --- a/app/templates/game_player.html +++ b/app/templates/game_player.html @@ -16,13 +16,13 @@ {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
- {% if player.user.auth_hash and not player.user.last_login %} + {% if player.user.auth_hash and not player.user.last_login and not player.user.password_hash %} - {% elif not player.user.last_login %} + {% elif not player.user.last_login and not player.user.password_hash %}
@@ -40,6 +40,7 @@ Player Name + Review Time @@ -50,6 +51,7 @@ {{ pcp.caught_player.user.name }} + {{ pcp.review.name.title() }} {{ moment(pcp.timestamp).fromNow() }}
+
+

Bunny Photo Review

+
+ {% for pcp in game.unreviewed_bunny_photos() %} + {% include '_review_photo.html' %} +
+ {% endfor %} +
+
+{% endblock %} \ No newline at end of file diff --git a/the_hunt.py b/the_hunt.py index de82fff..8e3005e 100755 --- a/the_hunt.py +++ b/the_hunt.py @@ -1,6 +1,6 @@ from app import create_app, db from app.models import Game, User, Objective, Location, Notification, GamePlayer, \ - PlayerFoundObjective, NotificationPlayer, PlayerCaughtPlayer, Role, GameState + PlayerFoundObjective, NotificationPlayer, PlayerCaughtPlayer, Role, GameState, Review app = create_app() @@ -10,7 +10,7 @@ def make_shell_context(): 'Location' : Location, 'Notification' : Notification, 'GamePlayer' : GamePlayer, 'PlayerFoundObjective' : PlayerFoundObjective, 'NotificationPlayer' : NotificationPlayer, 'PlayerCaughtPlayer' : PlayerCaughtPlayer, 'Role' : Role, 'GameState' : GameState, - 'create_objects' : create_objects} + 'Review' : Review, 'create_objects' : create_objects} def create_objects(): g1 = Game(name='TestGame') @@ -24,16 +24,28 @@ def create_objects(): u4 = User(name='Emma') u5 = User(name='Demi') + o1 = Objective(name='Florin', latitude=52.0932, longitude=5.12405) + o2 = Objective(name='Amsterdam', latitude=52.35547, longitude=5.12405) + o3 = Objective(name='Amersfoort', latitude=52.17056, longitude=5.12405) + + o1.set_hash() + o2.set_hash() + o3.set_hash() + g1.players.append(GamePlayer(user=u1, role=Role.owner)) g1.players.append(GamePlayer(user=u2, role=Role.hunter)) g1.players.append(GamePlayer(user=u3, role=Role.hunter)) g1.players.append(GamePlayer(user=u4, role=Role.bunny)) g1.players.append(GamePlayer(user=u5, role=Role.bunny)) + g1.objectives.append(o1) + g1.objectives.append(o2) + g1.objectives.append(o3) + g2.players.append(GamePlayer(user=u1, role=Role.bunny)) g2.players.append(GamePlayer(user=u2, role=Role.owner)) g2.players.append(GamePlayer(user=u3, role=Role.hunter)) db.session.add(g1) db.session.add(g2) - db.session.commit() \ No newline at end of file + db.session.commit()