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. 44
      app/templates/objective.html
  8. 2
      requirements.txt

4
app/errors.py

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

4
app/forms.py

@ -45,11 +45,11 @@ class CreateGameForm(FlaskForm): @@ -45,11 +45,11 @@ class CreateGameForm(FlaskForm):
date_time_utc = clientzone.localize(date_time.data).astimezone(timezone('UTC'))
date_time.data = date_time_utc
class AddObjectiveForm(FlaskForm):
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('Create')
submit = SubmitField('Save')
def validate_objective_name(self, objective_name):
if objective_name.data == '': return

12
app/models.py

@ -5,6 +5,9 @@ from sqlalchemy.ext.associationproxy import association_proxy @@ -5,6 +5,9 @@ from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.sql import func
from secrets import token_hex
from flask_login import UserMixin
import json
from json import JSONEncoder
class Role(Enum):
none = 0
@ -157,6 +160,15 @@ class Objective(db.Model): @@ -157,6 +160,15 @@ class Objective(db.Model):
def set_hash(self):
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):
__tablename__ = 'location'
id = db.Column(db.Integer, primary_key=True)

73
app/routes.py

@ -1,9 +1,12 @@ @@ -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 sqlalchemy import and_
from app import app, db
from app.models import Player, Game, Role, GamePlayer, Objective
from app.forms import LoginForm, RegistrationForm, CreateGameForm, AddObjectiveForm
from app.models import Player, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder
from app.forms import LoginForm, RegistrationForm, CreateGameForm, ObjectiveForm
import json
import qrcode
from io import BytesIO
@app.route('/')
@app.route('/index')
@ -64,18 +67,26 @@ def create_game(): @@ -64,18 +67,26 @@ def create_game():
@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()
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>')
@login_required
def game_player(game_name, player_name):
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'])
@login_required
def add_objective(game_name):
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():
objective = Objective(name=form.objective_name.data, longitude=form.longitude.data, latitude=form.latitude.data)
objective.set_hash()
@ -83,4 +94,54 @@ def add_objective(game_name): @@ -83,4 +94,54 @@ def add_objective(game_name):
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)
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 @@ @@ -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 @@ @@ -1,5 +1,15 @@
{% 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 %}
<h1>{{ game.name }} Dashboard</h1>
@ -37,27 +47,65 @@ @@ -37,27 +47,65 @@
<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>
<th scope="col">Hash</th>
<th scope="col"></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>
<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>
{% endfor %}
</tbody>
</table>
</div>
<div id="map" style=" height: 500px; border-radius: 10px; " class="col-md-6 col-xs-12"></div>
{% 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 %}

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

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

2
requirements.txt

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

Loading…
Cancel
Save