Browse Source

implement issue #03

feature_tests
Burathar 4 years ago
parent
commit
9d6670abdc
  1. 20
      app/forms.py
  2. 9
      app/models.py
  3. 31
      app/routes.py
  4. 82
      app/templates/add_objective.html
  5. 16
      app/templates/create_game.html
  6. 29
      app/templates/game_dashboard.html
  7. 2
      app/templates/register.html
  8. 12
      migrations/versions/3ef4c34115fd_updated_to_database_documentation_1_3.py

20
app/forms.py

@ -1,7 +1,7 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField, DateTimeField, BooleanField, HiddenField from wtforms import StringField, PasswordField, BooleanField, SubmitField, TextAreaField, DateTimeField, BooleanField, HiddenField, FloatField
from wtforms.validators import DataRequired, EqualTo, ValidationError, Length from wtforms.validators import DataRequired, EqualTo, ValidationError, Length, NumberRange
from app.models import Player from app.models import Player, Objective
from pytz import timezone from pytz import timezone
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
@ -29,7 +29,7 @@ class CreateGameForm(FlaskForm):
end_time_disabled = BooleanField('No end time') end_time_disabled = BooleanField('No end time')
end_time = DateTimeField(id='datetimepicker_end', format="%d-%m-%Y %H:%M") end_time = DateTimeField(id='datetimepicker_end', format="%d-%m-%Y %H:%M")
timezone = HiddenField(validators=[DataRequired()]) timezone = HiddenField(validators=[DataRequired()])
submit = SubmitField('Register') submit = SubmitField('Create')
def validate_start_time(self, start_time): def validate_start_time(self, start_time):
self.date_time_validator(self.start_time_disabled, start_time) self.date_time_validator(self.start_time_disabled, start_time)
@ -44,3 +44,15 @@ class CreateGameForm(FlaskForm):
clientzone = timezone(self.timezone.data) clientzone = timezone(self.timezone.data)
date_time_utc = clientzone.localize(date_time.data).astimezone(timezone('UTC')) date_time_utc = clientzone.localize(date_time.data).astimezone(timezone('UTC'))
date_time.data = date_time_utc date_time.data = date_time_utc
class AddObjectiveForm(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('Create')
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.')

9
app/models.py

@ -76,6 +76,7 @@ class Game(db.Model):
backref=db.backref('game', lazy='joined')) backref=db.backref('game', lazy='joined'))
class Player(UserMixin, db.Model): class Player(UserMixin, db.Model):
""" !Always call set_auth_hash() after creating new instance! """
__tablename__ = 'player' __tablename__ = 'player'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False) name = db.Column(db.String(64), unique=True, nullable=False)
@ -138,11 +139,12 @@ def load_user(id):
return Player.query.get(int(id)) return Player.query.get(int(id))
class Objective(db.Model): class Objective(db.Model):
""" !Always call set_hash after() creating new instance! """
__tablename__ = 'objective' __tablename__ = 'objective'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), nullable=False) name = db.Column(db.String(64))
game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False) game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False)
hash = db.Column(db.String(128), unique=True, nullable=False) hash = db.Column(db.String(32), unique=True, nullable=False)
longitude = db.Column(db.Numeric(precision=15, scale=10, asdecimal=False, decimal_return_scale=None)) # maybe check asdecimal and decimal_return_scale later? longitude = db.Column(db.Numeric(precision=15, scale=10, asdecimal=False, decimal_return_scale=None)) # maybe check asdecimal and decimal_return_scale later?
latitude = db.Column(db.Numeric(precision=15, scale=10, asdecimal=False, decimal_return_scale=None)) latitude = db.Column(db.Numeric(precision=15, scale=10, asdecimal=False, decimal_return_scale=None))
objective_found_by = db.relationship( objective_found_by = db.relationship(
@ -152,6 +154,9 @@ class Objective(db.Model):
found_by = association_proxy('objective_found_by', 'player', found_by = association_proxy('objective_found_by', 'player',
creator=lambda player: PlayerFoundObjective(player=player)) creator=lambda player: PlayerFoundObjective(player=player))
def set_hash(self):
self.hash = token_hex(16)
class Location(db.Model): class Location(db.Model):
__tablename__ = 'location' __tablename__ = 'location'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

31
app/routes.py

@ -2,11 +2,11 @@ from flask import render_template, flash, redirect, url_for, request
from flask_login import login_user, logout_user, current_user, login_required from flask_login import login_user, logout_user, current_user, login_required
from sqlalchemy import and_ from sqlalchemy import and_
from app import app, db from app import app, db
from app.models import Player, Game, Role, GamePlayer from app.models import Player, Game, Role, GamePlayer, Objective
from app.forms import LoginForm, RegistrationForm, CreateGameForm from app.forms import LoginForm, RegistrationForm, CreateGameForm, AddObjectiveForm
@app.route('/', methods=['GET']) @app.route('/')
@app.route('/index', methods=['GET']) @app.route('/index')
@login_required @login_required
def index(): def index():
return render_template("index.html", title='Home') return render_template("index.html", title='Home')
@ -26,6 +26,7 @@ def login():
return render_template('login.html', title='Sign In', form=form) return render_template('login.html', title='Sign In', form=form)
@app.route('/logout') @app.route('/logout')
@login_required
def logout(): def logout():
logout_user() logout_user()
return redirect(url_for('index')) return redirect(url_for('index'))
@ -38,15 +39,17 @@ def register():
if form.validate_on_submit(): if form.validate_on_submit():
player = Player(name=form.username.data) player = Player(name=form.username.data)
player.set_password(form.password.data) player.set_password(form.password.data)
player.set_auth_hash()
db.session.add(player) db.session.add(player)
db.session.commit() db.session.commit()
flash('Congratulations, you are now a registered user!') flash('Congratulations, you are now a registered user!')
return redirect(url_for('login')) return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form) return render_template('register.html', title='Register', form=form)
@login_required
@app.route('/create_game', methods=['GET', 'POST']) @app.route('/create_game', methods=['GET', 'POST'])
@login_required
def create_game(): def create_game():
print(current_user.is_authenticated)
form = CreateGameForm() form = CreateGameForm()
if form.validate_on_submit(): 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)
@ -57,13 +60,27 @@ def create_game():
return redirect(url_for('game_dashboard', game_name=game.name)) return redirect(url_for('game_dashboard', game_name=game.name))
return render_template('create_game.html', title='Create Game', form=form) return render_template('create_game.html', title='Create Game', form=form)
@login_required
@app.route('/game/<game_name>/dashboard') @app.route('/game/<game_name>/dashboard')
@login_required
def game_dashboard(game_name): 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(Game.game_players.any(and_(GamePlayer.player.has(Player.name == current_user.name), GamePlayer.role == 'owner'))).first_or_404()
return render_template('game_dashboard.html', title = 'Game Dashboard', game=game) return render_template('game_dashboard.html', title = 'Game Dashboard', game=game)
@login_required
@app.route('/game/<game_name>/player/<player_name>') @app.route('/game/<game_name>/player/<player_name>')
@login_required
def game_player(game_name, player_name): def game_player(game_name, player_name):
return redirect(url_for('indsex')) return redirect(url_for('indsex'))
@app.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()
form = AddObjectiveForm()
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('game_dashboard', game_name=game.name))
return render_template('add_objective.html', title = f'Add Objective for {game_name}', form=form)

82
app/templates/add_objective.html

@ -0,0 +1,82 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block head %}
{{ super() }}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin=""></script>
{% endblock %}
{% block app_content %}
<h1>Add Objective</h1>
<hr>
<div class="row">
<div class="col-md-4 col-sm-6 col-xs-8">
<form action="" method="post" class="form" role="form">
{{ form.hidden_tag() }}
{{ wtf.form_field(form.objective_name, class='form-control') }}
{{ wtf.form_field(form.latitude, type='number', value='52.2', min='-90', max='90', step='0.00001') }}
{{ wtf.form_field(form.longitude, class='form-control', type='number', value='5.3', min='-180', max='181', step='0.00001') }}
{{ wtf.form_field(form.submit, class='btn btn-primary') }}
</form>
</div>
<div id="map" style=" height: 500px; border-radius: 10px; " class="col-md-6 col-xs-12"></div>
</div>
<hr>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="text/javascript", crossorigin="anonymous">
// Leaflet Map
var map = L.map( 'map', {
center: [52.2, 5.3],
minZoom: 6,
maxZoom: 19,
bounds: [[50.5, 3.25], [54, 7.6]],
zoom: 8
});
L.control.scale().addTo(map);
L.tileLayer( 'https://geodata.nationaalgeoregister.nl/tiles/service/wmts/brtachtergrondkaartpastel/EPSG:3857/{z}/{x}/{y}.png', {
attribution: 'Kaartgegevens &copy; <a href="kadaster.nl">Kadaster</a>'
}).addTo( map );
var objectiveMarker = L.marker([
$("#latitude")[0].value,
$("#longitude")[0].value
], {
draggable: true
}).addTo(map);
var round = function(value){
return (Math.round(value * 100000) / 100000).toFixed(5);
};
objectiveMarker.on('dragend', function(e){
var newPosition = e.target.getLatLng();
$("#latitude")[0].value = round(newPosition.lat);
$("#longitude")[0].value = round(newPosition.lng);
})
$("#latitude").change(function(e) {
var newLatLng = new L.LatLng(e.target.value, objectiveMarker._latlng.lng);
objectiveMarker.setLatLng(newLatLng);
});
$("#longitude").change(function(e) {
var newLatLng = new L.LatLng(objectiveMarker._latlng.lat, e.target.value);
objectiveMarker.setLatLng(newLatLng);
});
</script>
{% endblock %}

16
app/templates/create_game.html

@ -3,8 +3,7 @@
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link rel="stylesheet" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css">
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css">
{% endblock %} {% endblock %}
{% block app_content %} {% block app_content %}
@ -13,7 +12,7 @@
<div class="col-md-4 col-sm-6 col-xs-8"> <div class="col-md-4 col-sm-6 col-xs-8">
<hr> <hr>
<form action="" method="post" class="form" role="form"> <form action="" method="post" class="form" role="form">
{{ form.csrf_token() }} {{ form.hidden_tag() }}
{{ form.timezone }} {{ form.timezone }}
{{ wtf.form_field(form.game_name, class='form-control') }} {{ wtf.form_field(form.game_name, class='form-control') }}
@ -36,22 +35,13 @@
{{ wtf.form_field(form.end_time_disabled, class='form-control') }} {{ wtf.form_field(form.end_time_disabled, class='form-control') }}
</div> </div>
</div> </div>
{{ wtf.form_field(form.submit, class='btn btn-primary') }}
<button type="submit" class="btn btn-primary">Submit</button>
</form> </form>
<hr> <hr>
</div> </div>
{% endblock %} {% endblock %}
TODO:
https://eonasdan.github.io/bootstrap-datetimepicker/Options/#locale+
- optie voor nu
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<!-- TODO: Scripts downloaden naar repo? --> <!-- TODO: Scripts downloaden naar repo? -->

29
app/templates/game_dashboard.html

@ -30,7 +30,34 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<h2>Objectives:</h2>
<p><a href="{{ url_for('add_objective', game_name = game.name) }}">Add new objective</a></p>
{% if game.objectives %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Hash</th>
<th scope="col">Objective Name</th>
<th scope="col">Latitude</th>
<th scope="col">Longitude</th>
<th scope="col">Amount of players that found it</th>
</tr>
</thead>
<tbody>
{% for objective in game.objectives %}
<tr>
<td>{{ objective.hash }}</td>
<td>{{ objective.name }}</td>
<td>{{ objective.latitude }}</td>
<td>{{ objective.longitude }}</td>
<td> Placeholder <td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %} {% endblock %}

2
app/templates/register.html

@ -8,4 +8,6 @@
{{ wtf.quick_form(form, button_map={'submit': 'primary'}) }} {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
</div> </div>
</div> </div>
<br>
<p>Already have an account? <a href="{{ url_for('login') }}">Click to Sign In!</a></p>
{% endblock %} {% endblock %}

12
migrations/versions/68009992b106_update_up_to_1_3_association_tables.py → migrations/versions/3ef4c34115fd_updated_to_database_documentation_1_3.py

@ -1,8 +1,8 @@
"""update up to 1.3+association tables """updated to database documentation 1.3
Revision ID: 68009992b106 Revision ID: 3ef4c34115fd
Revises: Revises:
Create Date: 2020-07-03 23:14:41.035088 Create Date: 2020-07-09 14:43:14.149353
""" """
from alembic import op from alembic import op
@ -10,7 +10,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '68009992b106' revision = '3ef4c34115fd'
down_revision = None down_revision = None
branch_labels = None branch_labels = None
depends_on = None depends_on = None
@ -64,9 +64,9 @@ def upgrade():
) )
op.create_table('objective', op.create_table('objective',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=False), sa.Column('name', sa.String(length=64), nullable=True),
sa.Column('game_id', sa.Integer(), nullable=False), sa.Column('game_id', sa.Integer(), nullable=False),
sa.Column('hash', sa.String(length=128), nullable=False), sa.Column('hash', sa.String(length=32), nullable=False),
sa.Column('longitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=True), sa.Column('longitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=True),
sa.Column('latitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=True), sa.Column('latitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=True),
sa.ForeignKeyConstraint(['game_id'], ['game.id'], ), sa.ForeignKeyConstraint(['game_id'], ['game.id'], ),
Loading…
Cancel
Save