diff --git a/app/main/routes.py b/app/main/routes.py index a831ce1..d90a1b4 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -12,7 +12,7 @@ from sqlalchemy import and_ from app import db from app.main import bp -from app.utils import generate_qr_code, serve_pil_image, flash_errors +from app.utils import generate_qr_code, serve_pil_image, flash_errors, get_game_if_owner from app.models import User, Game, Role, GamePlayer, GameState, Objective, ObjectiveMinimalEncoder,\ LocationEncoder, PlayerCaughtPlayer, Review, Location from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, \ @@ -41,7 +41,6 @@ def create_game(): game = Game(name=form.game_name.data, start_time=form.start_time.data, end_time=form.end_time.data) - game.update_state() game.players.append(GamePlayer(user=current_user, role=Role['owner'])) db.session.add(game) db.session.commit() @@ -54,9 +53,7 @@ def create_game(): @bp.route('/game//change_settings', methods=['GET', 'POST']) @login_required def change_game_settings(game_name): - game = Game.query.filter_by(name=game_name).first_or_404() - if not game.owned_by(current_user): - abort(403) + game = get_game_if_owner(game_name) form = CreateGameForm() form.old_name = game.name @@ -80,20 +77,17 @@ def change_game_settings(game_name): game.name = form.game_name.data game.start_time = form.start_time.data game.end_time = form.end_time.data - game.update_state() db.session.commit() flash(f"'{game.name}' had been updated!") return redirect(url_for('main.game_dashboard', game_name=game.name)) else: flash_errors(form) - return render_template('create_game.html', title='Chage Game Settings', form=form) + return render_template('edit_game.html', title='Chage Game Settings', form=form, game=game) @bp.route('/game//delete') @login_required def delete_game(game_name): - game = Game.query.filter_by(name=game_name).first_or_404() - if not game.owned_by(current_user): - abort(403) + game = get_game_if_owner(game_name) db.session.delete(game) for user in game.users: if not user.last_login: @@ -102,6 +96,45 @@ def delete_game(game_name): flash(f"Game '{game.name}' has been deleted!") return redirect(url_for('main.index')) +@bp.route('/game//unhide') +@bp.route('/game//publish') +@login_required +def publish_game(game_name): + game = get_game_if_owner(game_name) + game.hidden = False + db.session.commit() + flash(f"Game '{game.name}' has been published!") + return redirect(url_for('main.index')) + +@bp.route('/game//hide') +@login_required +def hide_game(game_name): + game = get_game_if_owner(game_name) + game.hidden = True + db.session.commit() + flash(f"Game '{game.name}' has been hidden!") + return redirect(url_for('main.index')) + +@bp.route('/game//pause') +@login_required +def pause_game(game_name): + game = get_game_if_owner(game_name) + game.paused = True + db.session.commit() + flash(f"Game '{game.name}' has been paused!") + return redirect(url_for('main.index')) + +@bp.route('/game//unpause') +@bp.route('/game//resume') +@login_required +def resume_game(game_name): + game = get_game_if_owner(game_name) + game.paused = False + db.session.commit() + flash(f"Game '{game.name}' has been paused!") + return redirect(url_for('main.index')) + + @bp.route('/game//dashboard') @login_required def game_dashboard(game_name): @@ -109,7 +142,7 @@ def game_dashboard(game_name): 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, + json=json, objective_encoder=ObjectiveMinimalEncoder, location_encoder=LocationEncoder) if role == Role.bunny: return render_template('game_bunny_dashboard.html', title='Game Dashboard', game=game, @@ -205,9 +238,7 @@ def get_caught_bunny_photo_directory(game): @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) + game = get_game_if_owner(game_name) 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: @@ -221,9 +252,7 @@ def review_caught_bunny_photos(game_name): @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 game.owned_by(current_user): - abort(403) + game = get_game_if_owner(game_name) form_add = PlayerAddForm() form_add.role.choices = [(role.value, role.name) for role in Role] @@ -250,9 +279,7 @@ def add_player(game_name): @bp.route('/game//removeplayer/') @login_required 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) + game = get_game_if_owner(game_name) user = User.query.filter(and_(User.name == username, User.games.contains(game))).first_or_404() game.users.remove(user) if not user.last_login: @@ -264,9 +291,7 @@ def remove_player(game_name, username): @bp.route('/game//player/', methods=['GET', 'POST']) @login_required def game_player(game_name, username): - game = Game.query.filter_by(name=game_name).first_or_404() - if not game.owned_by(current_user): - abort(403) + game = get_game_if_owner(game_name) user = User.query.filter((User.name == username) & (User.games.contains(game))).first_or_404() player = user.player_in(game) @@ -287,9 +312,7 @@ def game_player(game_name, username): @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 game.owned_by(current_user): - abort(403) + game = get_game_if_owner(game_name) form = ObjectiveForm() objective = Objective(name='', latitude=52.0932, longitude=5.12405) if form.validate_on_submit(): diff --git a/app/models/game.py b/app/models/game.py index 7491ba8..651d760 100644 --- a/app/models/game.py +++ b/app/models/game.py @@ -10,7 +10,9 @@ class Game(db.Model): __tablename__ = 'game' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), index=True, unique=True, nullable=False) - state = db.Column(db.Enum(GameState), server_default=GameState(1).name, nullable=False) + #state = db.Column(db.Enum(GameState), server_default=GameState(1).name, nullable=False) + hidden = db.Column(db.Boolean, server_default='True', nullable=False) + paused = db.Column(db.Boolean, server_default='False', nullable=False) start_time = db.Column(db.DateTime) end_time = db.Column(db.DateTime) players = db.relationship( @@ -68,40 +70,25 @@ class Game(db.Model): [player.player_caught_players for player in self.players] for pcp in pcps if pcp.review == Review.none] - def update_state(self): - ''' - Updates state according to game.start_time and game.end_time. - Returns True if state is changed. - ''' - #pylint: disable=no-member - old_state = self.state + def is_active(self): + return self.get_state == GameState.active + + def get_state(self): now = datetime.utcnow() - start = self.start_time.replace(tzinfo=None) or datetime.min + start = (self.start_time or datetime.min).replace(tzinfo=None) if now < start: # Before Game - if self.state == GameState.hidden: - return False - self.state = GameState.published + if self.hidden: + return GameState.hidden + return GameState.published - end = self.end_time.replace(tzinfo=None) or datetime.max + end = (self.end_time or datetime.max).replace(tzinfo=None) if start < now < end: # During Game - if self.state == GameState.interrupted: - return False - self.state = GameState.active + if self.paused: + return GameState.interrupted + return GameState.active if now > end: # After Game - if self.state == GameState.hidden: - return False - self.state = GameState.finished - - if self.state != old_state: - return True - return False - - def is_active(self): - self.update_state() - return self.state == GameState.active - - def get_state(self): - self.update_state() - return self.state + if self.hidden: + return GameState.hidden + return GameState.finished diff --git a/app/static/assets/utils.js b/app/static/assets/utils.js new file mode 100644 index 0000000..8a3af8f --- /dev/null +++ b/app/static/assets/utils.js @@ -0,0 +1,37 @@ +function createDateTimePicker(datePicker, checkbox, date){ + $(datePicker).datetimepicker({ + //useCurrent: false, //Important! See issue #1075 + locale: 'en-gb', + format: 'DD-MM-YYYY HH:mm', + keepInvalid: true, + sideBySide: true, + defaultDate: null, + timeZone: moment.tz.guess() + }); + + $(checkbox).change(function() { + updateDateTimePicker(datePicker, checkbox) + }); + + if (!$(checkbox)[0].checked){ + if ($(datePicker)[0].value == ''){ + $(datePicker)[0].value = date.format('DD-MM-YYYY HH:mm'); + } else if (!$('.alert')[0]) { //Don't convert datetime again after error + $(datePicker)[0].value = moment.utc($(datePicker)[0].value, 'DD-MM-YYYY HH:mm').local().format('DD-MM-YYYY HH:mm'); + } + } else { + $(datePicker).data("DateTimePicker").disable(); + }; +} + +function updateDateTimePicker(picker, checkbox){ + if ($(checkbox).prop("checked")) { + $(picker).data("DateTimePicker").disable(); + } + else { + $(picker).data("DateTimePicker").enable(); + if ($(picker)[0].value == ''){ + $(picker)[0].value = moment().format('DD-MM-YYYY HH:mm'); + } + } +} \ No newline at end of file diff --git a/app/templates/create_game.html b/app/templates/create_game.html index c3edbc2..d00fe8e 100644 --- a/app/templates/create_game.html +++ b/app/templates/create_game.html @@ -4,6 +4,7 @@ {% block head %} {{ super() }} + {% endblock %} {% block app_content %} @@ -35,11 +36,7 @@ {{ wtf.form_field(form.end_time_disabled, class='form-control') }} - {% if form.old_name %} - {{ wtf.form_field(form.submit, class='btn btn-primary', value="Update") }} - {% else %} {{ wtf.form_field(form.submit, class='btn btn-primary') }} - {% endif %}
@@ -54,66 +51,19 @@ {% endblock %} diff --git a/app/templates/edit_game.html b/app/templates/edit_game.html new file mode 100644 index 0000000..67185b4 --- /dev/null +++ b/app/templates/edit_game.html @@ -0,0 +1,97 @@ +{% extends 'base.html' %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block head %} +{{ super() }} + + +{% endblock %} + +{% block app_content %} +

{{ title }}

+ +
+
+
+ {{ form.hidden_tag() }} + {{ form.timezone }} + {{ wtf.form_field(form.game_name, class='form-control') }} + + {{ form.start_time.label }} +
+
+ {{ form.start_time }} +
+
+ {{ wtf.form_field(form.start_time_disabled, class='form-control') }} +
+
+ + {{ form.end_time.label }} +
+
+ {{ form.end_time }} +
+
+ {{ wtf.form_field(form.end_time_disabled, class='form-control') }} +
+
+ {{ wtf.form_field(form.submit, class='btn btn-primary', value="Update") }} +
+
+ {% if game.hidden %} + + + + {% else %} + + + + {% endif %} + + {% if game.paused %} + + + + {% else %} + + + + {% endif %} + + +
+ +{% endblock %} + +{% block scripts %} +{{ super() }} + + + + + +{% endblock %} diff --git a/app/templates/game_owner_dashboard.html b/app/templates/game_owner_dashboard.html index 42247f1..8773559 100644 --- a/app/templates/game_owner_dashboard.html +++ b/app/templates/game_owner_dashboard.html @@ -9,7 +9,6 @@ {% block app_content %}

{{ game.name }} Dashboard

- @@ -119,12 +118,5 @@ map.fitBounds(markers); } - //Delete Game button - function deleteGame() { - if (confirm("Are you sure you want to delete this game?")) { - window.location.href = "{{ url_for('main.delete_game', game_name=game.name) }}" - } - } - {% endblock %} diff --git a/app/utils.py b/app/utils.py index 5ca1dd7..4198643 100644 --- a/app/utils.py +++ b/app/utils.py @@ -1,6 +1,8 @@ from io import BytesIO import qrcode -from flask import send_file, flash +from flask import send_file, flash, abort +from flask_login import current_user +from app.models import Game def generate_qr_code(url): qr = qrcode.QRCode( @@ -29,3 +31,9 @@ def flash_errors(form): getattr(form, field).label.text, error ), 'error') + +def get_game_if_owner(game_name): + game = Game.query.filter_by(name=game_name).first_or_404() + if not game.owned_by(current_user): + abort(403) + return game