12 changed files with 457 additions and 0 deletions
			
			
		| @ -0,0 +1,6 @@ | |||||||
|  | 
 | ||||||
|  | from flask import Blueprint | ||||||
|  | 
 | ||||||
|  | bp = Blueprint('auth', __name__) | ||||||
|  | 
 | ||||||
|  | from app.auth import routes | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | 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 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LoginForm(FlaskForm): | ||||||
|  |     username = StringField('Username', validators=[DataRequired()]) | ||||||
|  |     password = PasswordField('Password', validators=[DataRequired()]) | ||||||
|  |     remember_me = BooleanField('Remember Me', default=True) | ||||||
|  |     submit = SubmitField('Sign In') | ||||||
|  | 
 | ||||||
|  | class RegistrationForm(FlaskForm): | ||||||
|  |     username = StringField('Username', validators=[DataRequired(), Length(min=0, max=64)]) | ||||||
|  |     password = PasswordField('Password', validators=[DataRequired(), Length(min=0, max=128)]) | ||||||
|  |     password2 = PasswordField( | ||||||
|  |         'Repeat Password', validators=[DataRequired(), EqualTo('password')]) | ||||||
|  |     submit = SubmitField('Register') | ||||||
|  | 
 | ||||||
|  |     def validate_username(self, username): | ||||||
|  |         player = Player.query.filter_by(name=username.data).first() | ||||||
|  |         if player is not None: | ||||||
|  |             raise ValidationError('Please use a different username.') | ||||||
| @ -0,0 +1,41 @@ | |||||||
|  | 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.auth.forms import LoginForm, RegistrationForm | ||||||
|  | 
 | ||||||
|  | @bp.route('/login', methods=['GET', 'POST']) | ||||||
|  | def login(): | ||||||
|  |     if current_user.is_authenticated: | ||||||
|  |         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): | ||||||
|  |             flash('Invalid username or password') | ||||||
|  |             return redirect(url_for('auth.login')) | ||||||
|  |         login_user(player, remember=form.remember_me.data) | ||||||
|  |         return redirect(url_for('main.index')) | ||||||
|  |     return render_template('login.html', title='Sign In', form=form) | ||||||
|  | 
 | ||||||
|  | @bp.route('/logout') | ||||||
|  | @login_required | ||||||
|  | def logout(): | ||||||
|  |     logout_user() | ||||||
|  |     return redirect(url_for('main.index')) | ||||||
|  | 
 | ||||||
|  | @bp.route('/register', methods=['GET', 'POST']) | ||||||
|  | def register(): | ||||||
|  |     if current_user.is_authenticated: | ||||||
|  |         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) | ||||||
|  |         db.session.commit() | ||||||
|  |         flash('Congratulations, you are now a registered user!') | ||||||
|  |         return redirect(url_for('auth.login')) | ||||||
|  |     return render_template('register.html', title='Register', form=form) | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | from flask import Blueprint | ||||||
|  | 
 | ||||||
|  | bp = Blueprint('errors', __name__) | ||||||
|  | 
 | ||||||
|  | from app.errors import handlers | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | from flask import render_template | ||||||
|  | from app import db | ||||||
|  | from app.errors import bp | ||||||
|  | 
 | ||||||
|  | @bp.app_errorhandler(403) | ||||||
|  | def forbidden_error(error): | ||||||
|  |     return render_template('errors/403.html'), 403 | ||||||
|  | 
 | ||||||
|  | @bp.app_errorhandler(404) | ||||||
|  | def not_found_error(error): | ||||||
|  |     return render_template('errors/404.html'), 404 | ||||||
|  | 
 | ||||||
|  | @bp.app_errorhandler(405) | ||||||
|  | def method_not_allowed_error(error): | ||||||
|  |     return render_template('errors/405.html'), 405 | ||||||
|  | 
 | ||||||
|  | @bp.app_errorhandler(500) | ||||||
|  | def internal_error(error): | ||||||
|  |     db.session.rollback() | ||||||
|  |     return render_template('errors/500.html'), 500 | ||||||
| @ -0,0 +1,5 @@ | |||||||
|  | from flask import Blueprint | ||||||
|  | 
 | ||||||
|  | bp = Blueprint('main', __name__) | ||||||
|  | 
 | ||||||
|  | from app.main import routes | ||||||
| @ -0,0 +1,54 @@ | |||||||
|  | from flask_wtf import FlaskForm | ||||||
|  | from wtforms import StringField, SubmitField, DateTimeField, BooleanField, HiddenField, FloatField, SelectField | ||||||
|  | from wtforms.validators import DataRequired, ValidationError, Length, NumberRange | ||||||
|  | from pytz import timezone | ||||||
|  | from app.models import Objective | ||||||
|  | 
 | ||||||
|  | class CreateGameForm(FlaskForm): | ||||||
|  |     game_name = StringField('Game Name', validators=[DataRequired(), Length(min=0, max=64)]) | ||||||
|  |     start_time_disabled = BooleanField('No start time') | ||||||
|  |     start_time = DateTimeField(id='datetimepicker_start', format="%d-%m-%Y %H:%M") | ||||||
|  |     end_time_disabled = BooleanField('No end time') | ||||||
|  |     end_time = DateTimeField(id='datetimepicker_end', format="%d-%m-%Y %H:%M") | ||||||
|  |     timezone = HiddenField(validators=[DataRequired()]) | ||||||
|  |     submit = SubmitField('Create') | ||||||
|  | 
 | ||||||
|  |     def validate_start_time(self, start_time): | ||||||
|  |         self.date_time_validator(self.start_time_disabled, start_time) | ||||||
|  | 
 | ||||||
|  |     def validate_end_time(self, end_time): | ||||||
|  |         self.date_time_validator(self.end_time_disabled, end_time) | ||||||
|  | 
 | ||||||
|  |     def date_time_validator(self, disabled, date_time): | ||||||
|  |         if disabled.data: | ||||||
|  |             date_time.data = None | ||||||
|  |             return | ||||||
|  |         clientzone = timezone(self.timezone.data) | ||||||
|  |         date_time_utc = clientzone.localize(date_time.data).astimezone(timezone('UTC')) | ||||||
|  |         date_time.data = date_time_utc | ||||||
|  | 
 | ||||||
|  | class ObjectiveForm(FlaskForm): | ||||||
|  |     objective_name = StringField('Objective Name', validators=[Length(min=0, max=64)]) | ||||||
|  |     latitude = FloatField('Latitude', validators=[DataRequired(), NumberRange(min=-90, max=90)]) | ||||||
|  |     longitude = FloatField('Longitude', validators=[DataRequired(), NumberRange(min=-180, max=180)]) | ||||||
|  |     submit = SubmitField('Save') | ||||||
|  | 
 | ||||||
|  |     def validate_objective_name(self, objective_name): | ||||||
|  |         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.') | ||||||
|  | 
 | ||||||
|  | class PlayerUpdateForm(FlaskForm): | ||||||
|  |     role = SelectField('Player Role', choices=[('none', 'none'), ('owner', 'owner'), ('hunter', 'hunter'), ('bunny', 'bunny')], validators=[DataRequired()]) | ||||||
|  |     submit = SubmitField('Update') | ||||||
|  | 
 | ||||||
|  | class PlayerAddForm(FlaskForm): | ||||||
|  |     name = StringField('Player Name', 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)]) | ||||||
|  |     role = SelectField('Player Role', choices=[('none', 'none'), ('owner', 'owner'), ('hunter', 'hunter'), ('bunny', 'bunny')], validators=[DataRequired()]) | ||||||
|  |     submit_create = SubmitField('Create') | ||||||
| @ -0,0 +1,187 @@ | |||||||
|  | import json | ||||||
|  | import qrcode | ||||||
|  | from flask import render_template, flash, redirect, url_for, request, abort, send_file | ||||||
|  | from flask_login import current_user, login_required | ||||||
|  | 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 | ||||||
|  | 
 | ||||||
|  | @bp.route('/') | ||||||
|  | @bp.route('/index') | ||||||
|  | @login_required | ||||||
|  | def index(): | ||||||
|  |     return render_template("index.html", title='Home') | ||||||
|  | 
 | ||||||
|  | @bp.route('/create_game', methods=['GET', 'POST']) | ||||||
|  | @login_required | ||||||
|  | def create_game(): | ||||||
|  |     print(current_user.is_authenticated) | ||||||
|  |     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'])) | ||||||
|  |         db.session.add(game) | ||||||
|  |         db.session.commit() | ||||||
|  |         flash(f"'{game.name}' had been created!") | ||||||
|  |         return redirect(url_for('main.game_dashboard', game_name=game.name)) | ||||||
|  |     return render_template('create_game.html', title='Create Game', form=form) | ||||||
|  | 
 | ||||||
|  | @bp.route('/game/<game_name>/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):  | ||||||
|  |         abort(403) | ||||||
|  |     return render_template('game_dashboard.html', title='Game Dashboard', game=game, json=json, objective_encoder=ObjectiveMinimalEncoder, location_encoder=LocationEncoder) | ||||||
|  | 
 | ||||||
|  | @bp.route('/game/<game_name>/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):  | ||||||
|  |         abort(403) | ||||||
|  |     form_add = PlayerAddForm() | ||||||
|  |     form_create = PlayerCreateForm() | ||||||
|  | 
 | ||||||
|  |     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])) | ||||||
|  |         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])) | ||||||
|  |         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) | ||||||
|  | 
 | ||||||
|  | @bp.route('/game/<game_name>/removeplayer/<player_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):  | ||||||
|  |         abort(403) | ||||||
|  |     player = Player.query.filter(and_(Player.name == player_name, Player.games.contains(game))).first_or_404() | ||||||
|  |     game.players.remove(player) | ||||||
|  |     db.session.commit() | ||||||
|  |     return redirect(url_for('main.game_dashboard', game_name=game.name)) | ||||||
|  | 
 | ||||||
|  | @bp.route('/game/<game_name>/player/<player_name>', methods=['GET', 'POST']) | ||||||
|  | @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):  | ||||||
|  |         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] | ||||||
|  |     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) | ||||||
|  | 
 | ||||||
|  | @bp.route('/player/<auth_hash>/qrcode.png') | ||||||
|  | @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): | ||||||
|  |         abort(403) | ||||||
|  |     img = generate_qr_code(url_for('main.player', auth_hash=auth_hash, _external=True)) | ||||||
|  |     return serve_pil_image(img) | ||||||
|  | 
 | ||||||
|  | @bp.route('/player/<auth_hash>') | ||||||
|  | @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) | ||||||
|  | 
 | ||||||
|  | '''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/<game_name>/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): | ||||||
|  |         abort(403) | ||||||
|  |     form = ObjectiveForm() | ||||||
|  |     objective = Objective(name='', latitude=52.0932, longitude=5.12405) | ||||||
|  |     if form.validate_on_submit(): | ||||||
|  |         objective = Objective(name=form.objective_name.data, longitude=form.longitude.data, latitude=form.latitude.data) | ||||||
|  |         objective.set_hash() | ||||||
|  |         game.objectives.append(objective) | ||||||
|  |         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) | ||||||
|  | 
 | ||||||
|  | @bp.route('/objective/<objective_hash>/delete', methods=['GET']) | ||||||
|  | @login_required | ||||||
|  | def delete_objective(objective_hash): | ||||||
|  |     objective = Objective.query.filter_by(hash=objective_hash).first_or_404() | ||||||
|  |     if not is_objective_owner(objective): | ||||||
|  |         abort(403) | ||||||
|  |     if is_objective_owner(objective): | ||||||
|  |         db.session.delete(objective) | ||||||
|  |         db.session.commit() | ||||||
|  |     return redirect(url_for('main.game_dashboard', game_name=objective.game.name)) | ||||||
|  | 
 | ||||||
|  | def generate_qr_code(url): | ||||||
|  |     qr = qrcode.QRCode( | ||||||
|  |         version=None, | ||||||
|  |         error_correction=qrcode.constants.ERROR_CORRECT_M, | ||||||
|  |         box_size=30, | ||||||
|  |         border=4, | ||||||
|  |     ) | ||||||
|  |     qr.add_data(url) | ||||||
|  |     qr.make(fit=True) | ||||||
|  |     return qr.make_image(fill_color='black', back_color='white') | ||||||
|  | 
 | ||||||
|  | # Source: https://stackoverflow.com/questions/7877282/how-to-send-image-generated-by-pil-to-browser | ||||||
|  | def serve_pil_image(pil_img): | ||||||
|  |     img_io = BytesIO() | ||||||
|  |     pil_img.save(img_io, 'PNG', quality=70) | ||||||
|  |     img_io.seek(0) | ||||||
|  |     return send_file(img_io, mimetype='image/png') | ||||||
|  | 
 | ||||||
|  | @bp.route('/objective/<objective_hash>/qrcode.png') | ||||||
|  | @login_required | ||||||
|  | def objective_qrcode(objective_hash): | ||||||
|  |     objective = Objective.query.filter_by(hash=objective_hash).first_or_404() | ||||||
|  |     if not is_objective_owner(objective): | ||||||
|  |         abort(403) | ||||||
|  |     img = generate_qr_code(url_for('main.objective', objective_hash=objective.hash, _external=True)) | ||||||
|  |     return serve_pil_image(img) | ||||||
|  | 
 | ||||||
|  | @bp.route('/objective/<objective_hash>', methods=['GET', 'POST']) | ||||||
|  | @login_required | ||||||
|  | def objective(objective_hash): | ||||||
|  |     objective = Objective.query.filter_by(hash=objective_hash).first_or_404() | ||||||
|  |     owner = is_objective_owner(objective) | ||||||
|  |     qrcode = generate_qr_code(objective) if owner else None | ||||||
|  |     form = ObjectiveForm() | ||||||
|  |     if form.submit.data and form.validate() and owner: | ||||||
|  |         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) | ||||||
| @ -0,0 +1,86 @@ | |||||||
|  | 
 | ||||||
|  | import unittest | ||||||
|  | from app import create_app, db | ||||||
|  | from app.models import Player, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder | ||||||
|  | import app.main.routes as routes | ||||||
|  | from config import Config | ||||||
|  | 
 | ||||||
|  | class TestConfig(Config): | ||||||
|  |     TESTING = True | ||||||
|  |     WTF_CSRF_ENABLED = False | ||||||
|  |     DEBUG = False | ||||||
|  |     SQLALCHEMY_DATABASE_URI = 'sqlite://' | ||||||
|  | 
 | ||||||
|  | class RoutesCase(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(routes.is_game_owner(g1, p1)) | ||||||
|  |         self.assertFalse(routes.is_game_owner(g1, 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(routes.is_player_game_owner(subject_player=p2, owner=p1), "owner owns subject_player's game") | ||||||
|  |         self.assertFalse(routes.is_player_game_owner(subject_player=p3, owner=p1), "owner doesn't own subject_player's game") | ||||||
|  |         self.assertTrue(routes.is_player_game_owner(subject_player=p1, owner=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(routes.is_objective_owner(o1, p1)) | ||||||
|  |         self.assertFalse(routes.is_objective_owner(o2, p1)) | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     unittest.main(verbosity=2) | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | {% extends 'base.html' %} | ||||||
|  | {% import 'bootstrap/wtf.html' as wtf %} | ||||||
|  | 
 | ||||||
|  | {% block app_content %} | ||||||
|  |     <h1>Sign In</h1> | ||||||
|  |     <div class="row"> | ||||||
|  |         <div class="col-md-4"> | ||||||
|  |             {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }} | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <br> | ||||||
|  |     <p>New User? <a href="{{ url_for('auth.register') }}">Click to Register!</a></p> | ||||||
|  | {% endblock %} | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  | {% import 'bootstrap/wtf.html' as wtf %} | ||||||
|  | 
 | ||||||
|  | {% block app_content %} | ||||||
|  |     <h1>Register</h1> | ||||||
|  |     <div class="row"> | ||||||
|  |         <div class="col-md-4"> | ||||||
|  |             {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }} | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <br> | ||||||
|  |     <p>Already have an account? <a href="{{ url_for('auth.login') }}">Click to Sign In!</a></p> | ||||||
|  | {% endblock %} | ||||||
					Loading…
					
					
				
		Reference in new issue