Browse Source

implement issuse #04 except bunnycount

feature_tests
Burathar 4 years ago
parent
commit
8b43313a6d
  1. 4
      app/errors.py
  2. 4
      app/forms.py
  3. 12
      app/models.py
  4. 73
      app/routes.py
  5. 7
      app/templates/403.html
  6. 54
      app/templates/game_dashboard.html
  7. 54
      app/templates/objective.html
  8. 2
      requirements.txt

4
app/errors.py

@ -1,6 +1,10 @@
from flask import render_template from flask import render_template
from app import app, db from app import app, db
@app.errorhandler(403)
def not_found_error(error):
return render_template('403.html'), 403
@app.errorhandler(404) @app.errorhandler(404)
def not_found_error(error): def not_found_error(error):
return render_template('404.html'), 404 return render_template('404.html'), 404

4
app/forms.py

@ -45,11 +45,11 @@ class CreateGameForm(FlaskForm):
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): class ObjectiveForm(FlaskForm):
objective_name = StringField('Objective Name', validators=[Length(min=0, max=64)]) objective_name = StringField('Objective Name', validators=[Length(min=0, max=64)])
latitude = FloatField('Latitude', validators=[DataRequired(), NumberRange(min=-90, max=90)]) latitude = FloatField('Latitude', validators=[DataRequired(), NumberRange(min=-90, max=90)])
longitude = FloatField('Longitude', validators=[DataRequired(), NumberRange(min=-180, max=180)]) longitude = FloatField('Longitude', validators=[DataRequired(), NumberRange(min=-180, max=180)])
submit = SubmitField('Create') submit = SubmitField('Save')
def validate_objective_name(self, objective_name): def validate_objective_name(self, objective_name):
if objective_name.data == '': return if objective_name.data == '': return

12
app/models.py

@ -5,6 +5,9 @@ from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.sql import func from sqlalchemy.sql import func
from secrets import token_hex from secrets import token_hex
from flask_login import UserMixin from flask_login import UserMixin
import json
from json import JSONEncoder
class Role(Enum): class Role(Enum):
none = 0 none = 0
@ -157,6 +160,15 @@ class Objective(db.Model):
def set_hash(self): def set_hash(self):
self.hash = token_hex(16) self.hash = token_hex(16)
class ObjectiveMinimalEncoder(JSONEncoder):
def default(self, objective):
return {
'name' : objective.name,
'hash' : objective.hash,
'longitude' : objective.longitude,
'latitude' : objective.latitude
}
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)

73
app/routes.py

@ -1,9 +1,12 @@
from flask import render_template, flash, redirect, url_for, request from flask import render_template, flash, redirect, url_for, request, abort, send_file
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, Objective from app.models import Player, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder
from app.forms import LoginForm, RegistrationForm, CreateGameForm, AddObjectiveForm from app.forms import LoginForm, RegistrationForm, CreateGameForm, ObjectiveForm
import json
import qrcode
from io import BytesIO
@app.route('/') @app.route('/')
@app.route('/index') @app.route('/index')
@ -64,18 +67,26 @@ def create_game():
@login_required @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, json=json, encoder=ObjectiveMinimalEncoder)
@app.route('/game/<game_name>/player/<player_name>') @app.route('/game/<game_name>/player/<player_name>')
@login_required @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'))
def is_game_owner(game):
return current_user in [gameplayer.player for gameplayer in game.game_players if gameplayer.role == Role.owner]
def is_objective_owner(objective):
return current_user in [gameplayer.player for gameplayer in objective.game.game_players if gameplayer.role == Role.owner]
@app.route('/game/<game_name>/add_objective', methods=['GET', 'POST']) @app.route('/game/<game_name>/add_objective', methods=['GET', 'POST'])
@login_required @login_required
def add_objective(game_name): def add_objective(game_name):
game = Game.query.filter_by(name = game_name).first_or_404() game = Game.query.filter_by(name = game_name).first_or_404()
form = AddObjectiveForm() if not is_game_owner(game): abort(403)
form = ObjectiveForm()
objective = Objective(name='', latitude=52.0932, longitude=5.12405)
if form.validate_on_submit(): if form.validate_on_submit():
objective = Objective(name=form.objective_name.data, longitude=form.longitude.data, latitude=form.latitude.data) objective = Objective(name=form.objective_name.data, longitude=form.longitude.data, latitude=form.latitude.data)
objective.set_hash() objective.set_hash()
@ -83,4 +94,54 @@ def add_objective(game_name):
db.session.commit() db.session.commit()
flash(f"Objective has been added!") flash(f"Objective has been added!")
return redirect(url_for('game_dashboard', game_name=game.name)) 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) return render_template('objective.html', title=f'Add Objective for {game_name}', form=form, objective=objective, owner=True)
def generate_objective_qr_code(objective):
qr = qrcode.QRCode(
version=None,
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=30,
border=4,
)
qr.add_data(url_for('objective', objective_hash=objective.hash, _external=True))
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')
@app.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_objective_qr_code(objective)
return serve_pil_image(img)
@app.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_objective_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 render_template('objective.html', title='Objective view', objective=objective, owner=owner, form=form, qrcode=qrcode)
@app.route('/objective/<objective_hash>/delete', methods=['GET'])
@login_required
def objective_delete(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('game_dashboard', game_name=objective.game.name))

7
app/templates/403.html

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block app_content %}
<h1>Forbidden</h1>
<h2>You don't have permission to do that</h2>
<p><a href="{{ url_for('index') }}">Back</a></p>
{% endblock %}

54
app/templates/game_dashboard.html

@ -1,5 +1,15 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% 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 %} {% block app_content %}
<h1>{{ game.name }} Dashboard</h1> <h1>{{ game.name }} Dashboard</h1>
@ -37,27 +47,65 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th scope="col">Hash</th>
<th scope="col">Objective Name</th> <th scope="col">Objective Name</th>
<th scope="col">Latitude</th> <th scope="col">Latitude</th>
<th scope="col">Longitude</th> <th scope="col">Longitude</th>
<th scope="col">Amount of players that found it</th> <th scope="col">Amount of players that found it</th>
<th scope="col">Hash</th>
<th scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for objective in game.objectives %} {% for objective in game.objectives %}
<tr> <tr>
<td>{{ objective.hash }}</td>
<td>{{ objective.name }}</td> <td>{{ objective.name }}</td>
<td>{{ objective.latitude }}</td> <td>{{ objective.latitude }}</td>
<td>{{ objective.longitude }}</td> <td>{{ objective.longitude }}</td>
<td> Placeholder <td> <td>Placeholder</td>
<td><a href="{{ url_for('objective', objective_hash = objective.hash) }}">{{ objective.hash }}</a></td>
<td><a href="{{ url_for('objective_delete', objective_hash = objective.hash) }}">
<button class="btn btn-danger">Delete</button></a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<div id="map" style=" height: 500px; border-radius: 10px; " class="col-md-6 col-xs-12"></div>
{% endif %} {% endif %}
{% 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 objectives = JSON.parse('{{ json.dumps(game.objectives, cls=encoder)|safe }}')
for (var i = 0; i < objectives.length; i++){
var objectiveMarker = L.marker([
objectives[i]['latitude'],
objectives[i]['longitude']
]).addTo(map);
objectiveMarker.bindTooltip(`<b>${objectives[i]['name']}</b><br>
${objectives[i]['hash']}`).openPopup();
}
</script>
{% endblock %} {% endblock %}

54
app/templates/add_objective.html → app/templates/objective.html

@ -12,19 +12,41 @@
{% endblock %} {% endblock %}
{% block app_content %} {% block app_content %}
{% if objective.hash is none %}
<h1>Add Objective</h1> <h1>Add Objective</h1>
{% elif objective.name == '' %}
<h1>Objective</h1>
{% else %}
<h1>Objective: {{ objective.name }}</h1>
{% endif %}
<hr> <hr>
<div class="row"> <div class="row">
<div class="col-md-4 col-sm-6 col-xs-8"> <div class="col-md-4 col-sm-6 col-xs-8">
<form action="" method="post" class="form" role="form"> {% if owner %}
{{ form.hidden_tag() }} <div class="row">
{{ wtf.form_field(form.objective_name, class='form-control') }} <form action="" method="post" class="form" role="form">
{{ wtf.form_field(form.latitude, type='number', value='52.2', min='-90', max='90', step='0.00001') }} {{ form.hidden_tag() }}
{{ wtf.form_field(form.longitude, class='form-control', type='number', value='5.3', min='-180', max='181', step='0.00001') }} {{ wtf.form_field(form.objective_name, value=objective.name, class='form-control') }}
{{ wtf.form_field(form.submit, class='btn btn-primary') }} {{ wtf.form_field(form.latitude, class='form-control', type='number', value=objective.latitude, min='-90', max='90', step='0.00001') }}
</form> {{ wtf.form_field(form.longitude, class='form-control', type='number', value=objective.longitude, min='-180', max='181', step='0.00001') }}
{{ wtf.form_field(form.submit, class='btn btn-primary') }}
</form>
{% if objective.hash %}
<a href="{{ url_for('objective_delete', objective_hash = objective.hash) }}">
<button class="btn btn-danger">Delete</button></a>
{% endif %}
</div>
<div class="row">
<img src="{{ url_for('objective_qrcode', objective_hash=objective.hash) }}" alt="qr_code_failed", width="100%">
</div>
{% else %}
<h2>latitude: {{ objective.latitude }}</h2>
<h2>longitude: {{ objective.longitude }}</h2>
{% endif %}
</div> </div>
<div id="map" style=" height: 500px; border-radius: 10px; " class="col-md-6 col-xs-12"></div> <div id="map" style=" height: 600px; border-radius: 10px; " class="col-md-6 col-xs-12"></div>
</div> </div>
<hr> <hr>
@ -36,11 +58,11 @@
<script type="text/javascript", crossorigin="anonymous"> <script type="text/javascript", crossorigin="anonymous">
// Leaflet Map // Leaflet Map
var map = L.map( 'map', { var map = L.map( 'map', {
center: [52.2, 5.3], center: ['{{ objective.latitude }}', '{{ objective.longitude }}'],
minZoom: 6, minZoom: 6,
maxZoom: 19, maxZoom: 19,
bounds: [[50.5, 3.25], [54, 7.6]], bounds: [[50.5, 3.25], [54, 7.6]],
zoom: 8 zoom: 10
}); });
L.control.scale().addTo(map); L.control.scale().addTo(map);
@ -48,12 +70,20 @@
attribution: 'Kaartgegevens &copy; <a href="kadaster.nl">Kadaster</a>' attribution: 'Kaartgegevens &copy; <a href="kadaster.nl">Kadaster</a>'
}).addTo( map ); }).addTo( map );
var objectiveMarker = L.marker([ if ('{{ owner }}' == 'True'){
var objectiveMarker = L.marker([
$("#latitude")[0].value, $("#latitude")[0].value,
$("#longitude")[0].value $("#longitude")[0].value
], { ], {
draggable: true draggable: true
}).addTo(map); })
} else {
var objectiveMarker = L.marker([
'{{ objective.latitude }}',
'{{ objective.longitude }}'
])
}
objectiveMarker.addTo(map);
var round = function(value){ var round = function(value){
return (Math.round(value * 100000) / 100000).toFixed(5); return (Math.round(value * 100000) / 100000).toFixed(5);

2
requirements.txt

@ -12,10 +12,12 @@ Jinja2==2.11.2
lazy-object-proxy==1.4.3 lazy-object-proxy==1.4.3
Mako==1.1.3 Mako==1.1.3
MarkupSafe==1.1.1 MarkupSafe==1.1.1
Pillow==7.2.0
python-dateutil==2.8.1 python-dateutil==2.8.1
python-dotenv==0.13.0 python-dotenv==0.13.0
python-editor==1.0.4 python-editor==1.0.4
pytz==2020.1 pytz==2020.1
qrcode==6.1
six==1.15.0 six==1.15.0
SQLAlchemy==1.3.18 SQLAlchemy==1.3.18
typed-ast==1.4.1 typed-ast==1.4.1

Loading…
Cancel
Save