Browse Source

implement photoreview

testing
Burathar 4 years ago
parent
commit
2bc24a47c2
  1. 3
      app/main/forms.py
  2. 53
      app/main/routes.py
  3. 1
      app/models/__init__.py
  4. 9
      app/models/game.py
  5. 19
      app/models/game_player.py
  6. 2
      app/models/player_caught_player.py
  7. 14
      app/models/review.py
  8. 1
      app/templates/_game_player_info.html
  9. 37
      app/templates/_review_photo.html
  10. 12
      app/templates/game_hunter_dashboard.html
  11. 10
      app/templates/game_owner_dashboard.html
  12. 6
      app/templates/game_player.html
  13. 14
      app/templates/review_caught_bunny_photos.html
  14. 16
      the_hunt.py

3
app/main/forms.py

@ -34,10 +34,13 @@ class ObjectiveForm(FlaskForm): @@ -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.')

53
app/main/routes.py

@ -13,7 +13,7 @@ from sqlalchemy import and_ @@ -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(): @@ -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): @@ -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): @@ -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/<game_name>/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/<game_name>/adduser', methods=['GET', 'POST'])
@bp.route('/game/<game_name>/addplayer', methods=['GET', 'POST'])
@ -176,7 +192,8 @@ def add_player(game_name): @@ -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/<game_name>/removeuser/<username>')
@bp.route('/game/<game_name>/removeplayer/<username>')
@ -229,7 +246,8 @@ def add_objective(game_name): @@ -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/<objective_hash>/delete', methods=['GET'])
@login_required
@ -269,17 +287,18 @@ def objective(objective_hash): @@ -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)

1
app/models/__init__.py

@ -7,5 +7,6 @@ from .notification_player import NotificationPlayer @@ -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

9
app/models/game.py

@ -3,6 +3,7 @@ from app import db @@ -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): @@ -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]

19
app/models/game_player.py

@ -7,6 +7,7 @@ from app import db @@ -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): @@ -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]

2
app/models/player_caught_player.py

@ -2,6 +2,7 @@ from sqlalchemy.sql import func @@ -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): @@ -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),

14
app/models/review.py

@ -0,0 +1,14 @@ @@ -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

1
app/templates/_game_player_info.html

@ -17,6 +17,5 @@ @@ -17,6 +17,5 @@
<th>End Time</th>
<td>{% if game.end_time %}{{ moment(game.end_time).format('DD-MM-YYYY, hh:mm')}}{% else %}-{% endif %}</td>
</tr>
</table>
</div>

37
app/templates/_review_photo.html

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
<div class="row">
<div class="col-md-4">
<div class="row">
<div class="table-responsive">
<table class="table">
<tr>
<th>Hunter</th>
<td>{{ pcp.catching_player.user.name }}</td>
</tr>
<tr>
<th>Bunny</th>
<td>{{ pcp.caught_player.user.name }}</td>
</tr>
<tr>
<th>Time</th>
<td>{{ pcp.timestamp.strftime('%Y-%m-%d %H:%M') }}</td>
</tr>
</table>
</div>
</div>
<div class="row">
<a href="{{ url_for('main.review_caught_bunny_photos', game_name=game.name, pcp_id=pcp.id, action='accept') }}">
<button class="btn btn-success">Accept</button>
</a>
<a href="{{ url_for('main.review_caught_bunny_photos', game_name=game.name, pcp_id=pcp.id, action='deny') }}">
<button class="btn btn-danger">Reject</button>
</a>
</div>
</div>
<div class="col-md-8">
<img src="{{ url_for('main.caught_bunny_photo', game_name=game.name,
timestamp=pcp.timestamp.strftime('%Y%m%d%H%M%S'),
bunny_name=pcp.caught_player.user.name,
hunter_name=pcp.catching_player.user.name) }}"
alt="could not load photo", width="100%">
</div>
</div>

12
app/templates/game_hunter_dashboard.html

@ -19,16 +19,24 @@ @@ -19,16 +19,24 @@
<thead>
<tr>
<th scope="col">Player Name</th>
<th scope="col">Times Caught</th>
<th scope="col">Times Caught
<span style="font-size: smaller;">
(<span style="color:green;">Accepted</span>/<span style="color:red;">Denied</span>/<span style="color:gray;">Not reviewed</span>)
</span>
</th>
<th scope="col">Last location</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% set player = current_user.player_in(game) %}
{% for bunny in game.bunnies() %}
<tr>
<td>{{ bunny.user.name }}</td>
<td>{{ bunny.player_caught_by_players | selectattr('catching_player', '==', current_user.player_in(game)) |list|length}}</td>
<td><span style="color:green;">{{ bunny.player_caught_by_players | selectattr('catching_player', '==', player) | selectattr('review.name', '==', 'accepted') |list|length}}</span> /
<span style="color:red;">{{ bunny.player_caught_by_players | selectattr('catching_player', '==', player) | selectattr('review.name', '==', 'denied') |list|length}}</span> /
<span style="color:gray;">{{ bunny.player_caught_by_players | selectattr('catching_player', '==', player) | selectattr('review.name', '==', 'none') |list|length}}</span>
</td>
<td>{% with location = bunny.last_location() %}
{% if location %}{{ moment(location.timestamp).fromNow()}}: {% endif %}
{{ location }}

10
app/templates/game_owner_dashboard.html

@ -9,7 +9,11 @@ @@ -9,7 +9,11 @@
{% block app_content %}
<h1>{{ game.name }} Dashboard</h1>
{% if game.unreviewed_bunny_photos() %}
<a href="{{ url_for('main.review_caught_bunny_photos', game_name=game.name) }}">
<button class="btn btn-primary">Review Bunny Photos</button>
</a>
{% endif %}
<h2>Players:</h2>
<p><a href="{{ url_for('main.add_player', game_name = game.name) }}">Add player</a></p>
<div class="table-responsive">
@ -31,8 +35,8 @@ @@ -31,8 +35,8 @@
<td><a href="{{ url_for('main.game_player', game_name = game.name, username = player.user.name) }}">{{ player.user.name }}</a></td>
<td>{{ player.role.name }}</td>
<td>{{ player.found_objectives | list | length }}</td>
<td>{{ player.caught_players | list | length }}</td>
<td>{{ player.caught_by_players | list | length }}</td>
<td>{{ player.accepted_caught_players() | list | length }}</td>
<td>{{ player.accepted_caught_by_players() | list | length }}</td>
<td>{% with location = player.last_location() %}
{% if location %}{{ moment(location.timestamp).fromNow()}}: {% endif %}
{{ location }}

6
app/templates/game_player.html

@ -16,13 +16,13 @@ @@ -16,13 +16,13 @@
{{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
</div>
{% 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 %}
<div class="row">
<a href="{{ url_for('auth.user_hash_login', auth_hash=player.user.auth_hash) }}">
<img src="{{ url_for('auth.user_qrcode', auth_hash=player.user.auth_hash) }}" alt="qr_code_failed" width="80%" title="login code for {{ player.user.name }}">
</a>
</div>
{% elif not player.user.last_login %}
{% elif not player.user.last_login and not player.user.password_hash %}
<br>
<div class="row">
<a href="#" , id="generate_auth_hash">
@ -40,6 +40,7 @@ @@ -40,6 +40,7 @@
<thead>
<tr>
<th scope="col">Player Name</th>
<th scope="col">Review</th>
<th scope="col">Time</th>
<th scope="col"></th>
</tr>
@ -50,6 +51,7 @@ @@ -50,6 +51,7 @@
<td><a
href="{{ url_for('main.game_player', game_name=player.game.name, username = pcp.caught_player.user.name) }}">{{ pcp.caught_player.user.name }}</a>
</td>
<td>{{ pcp.review.name.title() }}</td>
<td>{{ moment(pcp.timestamp).fromNow() }}</td>
<td><a href="{{ url_for('main.caught_bunny_photo',
game_name=player.game.name,

14
app/templates/review_caught_bunny_photos.html

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block app_content %}
<div class="col-md-1"></div>
<div class="col-md-10">
<h1>Bunny Photo Review</h1>
<hr>
{% for pcp in game.unreviewed_bunny_photos() %}
{% include '_review_photo.html' %}
<hr>
{% endfor %}
</div>
<div class="col.md-1"></div>
{% endblock %}

16
the_hunt.py

@ -1,6 +1,6 @@ @@ -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(): @@ -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,12 +24,24 @@ def create_objects(): @@ -24,12 +24,24 @@ 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))

Loading…
Cancel
Save