Browse Source

implement hunter dashboard, inc catch bunny

testing
Burathar 4 years ago
parent
commit
713d4ddee3
  1. 1
      .gitignore
  2. 3
      app/__init__.py
  3. 6
      app/main/forms.py
  4. 75
      app/main/routes.py
  5. 3
      app/models/player_caught_player.py
  6. 2
      app/templates/game_hunter_dashboard.html
  7. 110
      app/templates/game_player.html
  8. 4
      config.py
  9. 121
      migrations/versions/c2c2f3cf0c14_reset_migrations.py
  10. BIN
      uploads/TestGame/caught_bunny_photos/20200719225209_Rogier_caught_Emma

1
.gitignore vendored

@ -145,3 +145,4 @@ cython_debug/
# the-hunt specific: # the-hunt specific:
logs/ logs/
app.db app.db
uploads/

3
app/__init__.py

@ -10,6 +10,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_login import LoginManager from flask_login import LoginManager
from flask_moment import Moment from flask_moment import Moment
from flask_wtf import CSRFProtect
from config import Config from config import Config
@ -20,6 +21,7 @@ login = LoginManager()
login.login_view = 'auth.login' login.login_view = 'auth.login'
login.login_message = 'Please log in to access this page.' login.login_message = 'Please log in to access this page.'
moment = Moment() moment = Moment()
csrf = CSRFProtect()
def create_app(config_class=Config): def create_app(config_class=Config):
# pylint: disable=no-member # pylint: disable=no-member
@ -31,6 +33,7 @@ def create_app(config_class=Config):
migrate.init_app(app, db) migrate.init_app(app, db)
login.init_app(app) login.init_app(app)
moment.init_app(app) moment.init_app(app)
csrf.init_app(app)
from app.errors import bp as errors_bp from app.errors import bp as errors_bp
app.register_blueprint(errors_bp) app.register_blueprint(errors_bp)

6
app/main/forms.py

@ -1,8 +1,10 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed, FileRequired
from wtforms import StringField, SubmitField, DateTimeField, BooleanField, HiddenField, FloatField, SelectField from wtforms import StringField, SubmitField, DateTimeField, BooleanField, HiddenField, FloatField, SelectField
from wtforms.validators import InputRequired, DataRequired, ValidationError, Length, NumberRange from wtforms.validators import InputRequired, DataRequired, ValidationError, Length, NumberRange
from pytz import timezone from pytz import timezone
from app.models import Objective from app.models import Objective
from app import Config
class CreateGameForm(FlaskForm): class CreateGameForm(FlaskForm):
game_name = StringField('Game Name', validators=[InputRequired(), Length(min=0, max=64)]) game_name = StringField('Game Name', validators=[InputRequired(), Length(min=0, max=64)])
@ -41,7 +43,7 @@ class ObjectiveForm(FlaskForm):
raise ValidationError('Please use a different name.') raise ValidationError('Please use a different name.')
class PlayerUpdateForm(FlaskForm): class PlayerUpdateForm(FlaskForm):
role = SelectField('Player Role', choices=[('none', 'none'), ('owner', 'owner'), ('hunter', 'hunter'), ('bunny', 'bunny')], validators=[InputRequired()]) role = SelectField('Player Role', coerce=int, validators=[InputRequired()])
submit = SubmitField('Update') submit = SubmitField('Update')
class PlayerAddForm(FlaskForm): class PlayerAddForm(FlaskForm):
@ -56,5 +58,5 @@ class UserCreateForm(FlaskForm):
class CatchBunnyForm(FlaskForm): class CatchBunnyForm(FlaskForm):
bunny = SelectField('Bunny Name', coerce=int, validators=[InputRequired()]) bunny = SelectField('Bunny Name', coerce=int, validators=[InputRequired()])
#photo photo = FileField('Upload Photo', validators=[FileRequired(), FileAllowed(Config.ALLOWED_PHOTO_EXTENSIONS, 'Images only!')])
submit = SubmitField('Send') submit = SubmitField('Send')

75
app/main/routes.py

@ -1,9 +1,14 @@
import fnmatch
import json import json
from os import listdir
from pathlib import Path
from datetime import datetime from datetime import datetime
from io import BytesIO from io import BytesIO
import qrcode import qrcode
from flask import render_template, flash, redirect, url_for, request, abort, send_file from flask import render_template, flash, redirect, url_for, request, abort, send_file, current_app, send_from_directory
from flask_login import current_user, login_required from flask_login import current_user, login_required
from werkzeug.utils import secure_filename
from werkzeug.security import safe_join
from sqlalchemy import and_ from sqlalchemy import and_
from app import db from app import db
@ -11,6 +16,9 @@ from app.main import bp
from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder, PlayerCaughtPlayer from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder, PlayerCaughtPlayer
from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm, CatchBunnyForm from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm, CatchBunnyForm
import os
@bp.before_app_request @bp.before_app_request
def before_request(): def before_request():
if current_user.is_authenticated: if current_user.is_authenticated:
@ -81,11 +89,11 @@ def catch_bunny(game_name):
flash('Only hunters can catch bunnies!') flash('Only hunters can catch bunnies!')
abort(403) abort(403)
game_bunnies = [gameplayer for gameplayer in game.players if gameplayer.role == Role.bunny] game_bunnies = game.bunnies()
form = CatchBunnyForm() form = CatchBunnyForm()
form.bunny.choices = [(player.user.id, player.user.name) for player in game_bunnies] form.bunny.choices = [(player.user.id, player.user.name) for player in game_bunnies]
if request.method == 'GET':
parsed_bunny_name = request.args.get('bunny_name', default='', type=str) parsed_bunny_name = request.args.get('bunny_name', default='', type=str)
if parsed_bunny_name: if parsed_bunny_name:
bunny = [gameplayer for gameplayer in game_bunnies if gameplayer.user.name == parsed_bunny_name] bunny = [gameplayer for gameplayer in game_bunnies if gameplayer.user.name == parsed_bunny_name]
@ -96,13 +104,59 @@ def catch_bunny(game_name):
if form.validate_on_submit(): if form.validate_on_submit():
bunny = [gameplayer for gameplayer in game_bunnies if gameplayer.user.id == form.bunny.data] bunny = [gameplayer for gameplayer in game_bunnies if gameplayer.user.id == form.bunny.data]
pcp = PlayerCaughtPlayer(catching_player=current_user.player_in(game), caught_player=bunny[0], photo_reference='To be Impemented') if not bunny:
flash('Please choose a bunny')
return request.url
bunny = bunny[0]
pcp = PlayerCaughtPlayer(catching_player=current_user.player_in(game), caught_player=bunny, timestamp=datetime.utcnow())
save_player_caught_player_photo(form.photo.data, game, pcp)
db.session.add(pcp) db.session.add(pcp)
db.session.commit() db.session.commit()
flash(f"You caught '{bunny[0].user.name}'! The submitted photo will be reviewd by a game owner.") flash(f"You caught {bunny.user.name}! The submitted photo will be reviewed by a game owner.")
return redirect(url_for('main.game_dashboard', game_name=game.name)) return redirect(url_for('main.game_dashboard', game_name=game.name))
return render_template('catch_bunny.html', title='Catch Bunny', form=form, game=game) return render_template('catch_bunny.html', title='Catch Bunny', form=form, game=game)
def save_player_caught_player_photo(file_storage, game, pcp):
timestamp = pcp.timestamp.strftime('%Y%m%d%H%M%S')
extension = Path(file_storage.filename).suffix
filename = secure_filename(f'{timestamp}_{pcp.catching_player.user.name}_caught_{pcp.caught_player.user.name}{extension}')
path = get_caught_bunny_photo_directory(game)
path.mkdir(parents=True, exist_ok=True)
file_storage.save(path / filename)
@bp.route('/game/<game_name>/caught_bunny_photo', methods=['GET'])
@login_required
def caught_bunny_photo(game_name):
game = Game.query.filter_by(name=game_name).first_or_404()
timestamp = request.args['timestamp']
bunny_name = request.args['bunny_name']
hunter_name = request.args['hunter_name']
hunter = GamePlayer.query.join(User).filter(
(GamePlayer.user_id == User.id) &
(GamePlayer.game_id == game.id) &
(User.name == hunter_name) &
(GamePlayer.role == Role.hunter)).first_or_404()
if not (game.owned_by(current_user) or current_user.player_in(game) == hunter):
abort(403)
directory = get_caught_bunny_photo_directory(game)
filename = get_bunny_photo_filename(directory, timestamp, hunter_name, bunny_name)
photo_path = safe_join(directory, filename)
#TODO: Implement switch between serve self and serve by webserver
return send_file(photo_path, conditional=True, as_attachment=False)
def get_bunny_photo_filename(directory, timestamp, hunter_name, bunny_name):
filename = secure_filename(f'{timestamp}_{hunter_name}_caught_{bunny_name}') + '.*'
matches = fnmatch.filter(listdir(directory), filename)
return matches[0] if matches else ''
def get_caught_bunny_photo_directory(game):
return Path(current_app.root_path).parent / \
current_app.config['UPLOAD_FOLDER'] / \
secure_filename(game.name) / \
current_app.config['PLAYER_CAUGHT_PLAYER_PHOTO_DIR_NAME']
@bp.route('/game/<game_name>/adduser', methods=['GET', 'POST']) @bp.route('/game/<game_name>/adduser', methods=['GET', 'POST'])
@bp.route('/game/<game_name>/addplayer', methods=['GET', 'POST']) @bp.route('/game/<game_name>/addplayer', methods=['GET', 'POST'])
@login_required @login_required
@ -149,15 +203,20 @@ def game_player(game_name, username):
game = Game.query.filter_by(name=game_name).first_or_404() game = Game.query.filter_by(name=game_name).first_or_404()
if not game.owned_by(current_user): if not game.owned_by(current_user):
abort(403) abort(403)
user = game.users user = User.query.filter((User.name == username) & (User.games.contains(game))).first_or_404()
user = User.query.filter(and_(User.name == username, User.games.contains(game))).first_or_404()
player = user.player_in(game) player = user.player_in(game)
# pylint: disable=no-member
form = PlayerUpdateForm(role=player.role.name) form = PlayerUpdateForm(role=player.role.name)
form.role.choices = [(role.value, role.name) for role in Role]
form.role.default = player.role.value
form.process()
if form.validate_on_submit(): if form.validate_on_submit():
player.role = Role[form.role.data] player.role = Role[form.role.data]
db.session.commit() db.session.commit()
return redirect(url_for('main.game_dashboard', game_name=game.name)) return redirect(url_for('main.game_dashboard', game_name=game.name))
return render_template('game_player.html', title=f'{user.name} in {game_name}', game=game, user=user, form=form, json=json, location_encoder=LocationEncoder) return render_template('game_player.html', title=f'{user.name} in {game_name}', player=player, form=form, json=json, location_encoder=LocationEncoder)
@bp.route('/user/<auth_hash>/qrcode.png') @bp.route('/user/<auth_hash>/qrcode.png')
@login_required @login_required

3
app/models/player_caught_player.py

@ -8,7 +8,6 @@ class PlayerCaughtPlayer(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True, server_default='-1') id = db.Column(db.Integer, primary_key=True, autoincrement=True, server_default='-1')
catching_player_id = db.Column(db.Integer, db.ForeignKey('game_player.id'), nullable=False) catching_player_id = db.Column(db.Integer, db.ForeignKey('game_player.id'), nullable=False)
caught_player_id = db.Column(db.Integer, db.ForeignKey('game_player.id'), nullable=False) caught_player_id = db.Column(db.Integer, db.ForeignKey('game_player.id'), nullable=False)
photo_reference = db.Column(db.String(128), unique=True, nullable=False)
timestamp = db.Column(db.DateTime, server_default=func.now(), nullable=False) timestamp = db.Column(db.DateTime, server_default=func.now(), nullable=False)
catching_player = db.relationship('GamePlayer', primaryjoin=(catching_player_id == GamePlayer.id), catching_player = db.relationship('GamePlayer', primaryjoin=(catching_player_id == GamePlayer.id),
backref=db.backref('player_caught_players', cascade='save-update, merge, delete, delete-orphan')) backref=db.backref('player_caught_players', cascade='save-update, merge, delete, delete-orphan'))
@ -20,7 +19,7 @@ This relation doesn't work as well as the others, and must be used as folowed:
g = Game.query.first() g = Game.query.first()
p1 = User.query[2].player_in(g) p1 = User.query[2].player_in(g)
p2 = User.query[3].player_in(g) p2 = User.query[3].player_in(g)
pc = PlayerCaughtPlayer(caught_player=p2, catching_player=p1, photo_reference='reference') pc = PlayerCaughtPlayer(caught_player=p2, catching_player=p1)
db.session.add(pc) db.session.add(pc)
db.session.commit() db.session.commit()

2
app/templates/game_hunter_dashboard.html

@ -33,7 +33,7 @@
{{ location }} {{ location }}
{% endwith %}</td> {% endwith %}</td>
<td> <td>
<a href="{{ url_for('main.catch_bunny', game_name=game.name, bunny_name=bunny.name) }}"> <a href="{{ url_for('main.catch_bunny', game_name=game.name, bunny_name=bunny.user.name) }}">
<button class="btn btn-success">Catch</button> <button class="btn btn-success">Catch</button>
</a> </a>
</td> </td>

110
app/templates/game_player.html

@ -8,55 +8,86 @@
{% endblock %} {% endblock %}
{% block app_content %} {% block app_content %}
<h1>Player: {{ user.name }}</h1> <h1>Player: {{ player.user.name }}</h1>
<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">
<div class="row"> <div class="row">
<form action="" method="post" class="form" role="form"> {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
{{ form.hidden_tag() }}
{% for gameplayer in user.user_games if gameplayer.game == game %}
{{ wtf.form_field(form.role, class='form-control') }}
{% endfor %}
{{ wtf.form_field(form.submit, class='btn btn-primary', value='Update') }}
</form>
</div> </div>
{% if user.auth_hash and not user.last_login %} {% if player.user.auth_hash and not player.user.last_login %}
<div class="row"> <div class="row">
<img src="{{ url_for('main.user_qrcode', auth_hash=user.auth_hash) }}" alt="qr_code_failed", width="100%"> <img src="{{ url_for('main.user_qrcode', auth_hash=player.user.auth_hash) }}" alt="qr_code_failed" , width="80%">
</div> </div>
{% elif not user.last_login %} {% elif not player.user.last_login %}
<br> <br>
<div class="row"> <div class="row">
<a href="#", id="generate_auth_hash"> <a href="#" , id="generate_auth_hash">
<button class="btn btn-success">Generate Login Code</button></a> <button class="btn btn-success">Generate Login Code</button></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div id="map" style=" height: 600px; border-radius: 10px; " class="col-md-6 col-xs-12"></div> <div id="map" style=" height: 400px; border-radius: 10px; " class="col-md-6 col-xs-12"></div>
</div>
{% if player.caught_players %}
<div class="row">
<h2>Caught Players:</h2>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Player Name</th>
<th scope="col">Time</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for pcp in player.player_caught_players %}
<tr>
<td><a
href="{{ url_for('main.game_player', game_name=player.game.name, username = pcp.caught_player.user.name) }}">{{ pcp.caught_player.user.name }}</a>
</td>
<td>{{ moment(pcp.timestamp).fromNow() }}</td>
<td><a href="{{ url_for('main.caught_bunny_photo',
game_name=player.game.name,
timestamp=pcp.timestamp.strftime('%Y%m%d%H%M%S'),
bunny_name=pcp.caught_player.user.name,
hunter_name=pcp.catching_player.user.name) }}">
<button class="btn btn-primary">Photo</button></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
{% endif %}
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
{{ moment.include_moment() }} {{ moment.include_moment() }}
<script type="text/javascript", crossorigin="anonymous"> <script type="text/javascript" , crossorigin="anonymous">
// Leaflet Map // Leaflet Map
'{% set last_location = user.last_location(game) %}' '{% set last_location = player.last_location() %}'
var map = L.map( 'map', { var map = L.map('map', {
center: ['{{ last_location.latitude or 52.2 }}', '{{ last_location.longitude or 5.3 }}'], center: ['{{ last_location.latitude or 52.2 }}', '{{ last_location.longitude or 5.3 }}'],
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: 9 zoom: 9
}); });
L.control.scale().addTo(map); L.control.scale().addTo(map);
L.tileLayer( 'https://geodata.nationaalgeoregister.nl/tiles/service/wmts/brtachtergrondkaartpastel/EPSG:3857/{z}/{x}/{y}.png', { L.tileLayer(
'https://geodata.nationaalgeoregister.nl/tiles/service/wmts/brtachtergrondkaartpastel/EPSG:3857/{z}/{x}/{y}.png', {
attribution: 'Kaartgegevens &copy; <a href="https://kadaster.nl">Kadaster</a>' attribution: 'Kaartgegevens &copy; <a href="https://kadaster.nl">Kadaster</a>'
}).addTo( map ); }).addTo(map);
var greenIcon = new L.Icon({ var greenIcon = new L.Icon({
iconUrl: "{{ url_for('static', filename='assets/leaflet/images/marker-icon-2x-green.png') }}", iconUrl: "{{ url_for('static', filename='assets/leaflet/images/marker-icon-2x-green.png') }}",
@ -67,42 +98,41 @@
shadowSize: [41, 41] shadowSize: [41, 41]
}); });
var locations = JSON.parse('{{ json.dumps(user.locations_during_game(game), cls=location_encoder)|safe }}') var locations = JSON.parse('{{ json.dumps(player.locations_during_game(), cls=location_encoder)|safe }}')
if (locations == null) {locations=[]} if (locations == null) {
for (var i = 0; i < locations.length; i++){ locations = []
}
for (var i = 0; i < locations.length; i++) {
var playerMarker = L.marker([ var playerMarker = L.marker([
locations[i]['latitude'], locations[i]['latitude'],
locations[i]['longitude'] locations[i]['longitude']
], {icon: greenIcon}).addTo(map); ], {
icon: greenIcon
}).addTo(map);
var timestamp_utc = moment.utc(locations[i]['timestamp_utc']).toDate() var timestamp_utc = moment.utc(locations[i]['timestamp_utc']).toDate()
var timestamp_local = moment(timestamp_utc).local().format('YYYY-MM-DD HH:mm'); var timestamp_local = moment(timestamp_utc).local().format('YYYY-MM-DD HH:mm');
playerMarker.bindTooltip(`<b>${locations[i]['username']}</b><br> playerMarker.bindTooltip(`<b>${locations[i]['username']}</b><br>
${timestamp_local}`).openPopup(); ${timestamp_local}`).openPopup();
} }
var latlngs = [ if (locations.length > 0) {
[[45.51, -122.68], var polyline = L.polyline(locations.map(l => [l.latitude, l.longitude]), {
[37.77, -122.43], color: 'red'
[34.04, -118.2]], }).addTo(map);
[[40.78, -73.91],
[41.83, -87.62],
[32.76, -96.72]]
];
if (locations.length > 0){
var polyline = L.polyline(locations.map(l => [l.latitude, l.longitude]), {color: 'red'}).addTo(map);
// zoom the map to the polyline // zoom the map to the polyline
map.fitBounds(polyline.getBounds()); map.fitBounds(polyline.getBounds());
} }
//Ajax for Generate Login Code button //Ajax for Generate Code button
$(function() { $(function () {
$('a#generate_auth_hash').bind('click', function() { $('a#generate_auth_hash').bind('click', function () {
$.ajax({url: "{{ url_for('auth.generate_auth_hash', username=user.name) }}", $.ajax({
success: function(result) { url: "{{ url_for('auth.generate_auth_hash', username=player.user.name) }}",
success: function (result) {
location.reload(); location.reload();
}}); }
});
}); });
}); });
</script> </script>
{% endblock %} {% endblock %}

4
config.py

@ -20,3 +20,7 @@ class Config(object):
ADMINS = ['your-email@example.com'] ADMINS = ['your-email@example.com']
BOOTSTRAP_SERVE_LOCAL = True BOOTSTRAP_SERVE_LOCAL = True
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER') or 'uploads'
PLAYER_CAUGHT_PLAYER_PHOTO_DIR_NAME = os.environ.get('PLAYER_CAUGHT_PLAYER_PHOTO_DIR_NAME') or 'caught_bunny_photos'
ALLOWED_PHOTO_EXTENSIONS = os.environ.get('ALLOWED_EXTENSIONS') or {'png', 'jpg', 'jpeg', 'gif', 'tiff', 'heif', 'heic'}

121
migrations/versions/c2c2f3cf0c14_reset_migrations.py

@ -1,121 +0,0 @@
"""reset migrations
Revision ID: c2c2f3cf0c14
Revises:
Create Date: 2020-07-19 04:46:04.972644
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c2c2f3cf0c14'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('game',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=False),
sa.Column('state', sa.Enum('initiated', 'published', 'started', 'interrupted', 'finished', name='gamestate'), server_default='initiated', nullable=False),
sa.Column('start_time', sa.DateTime(), nullable=True),
sa.Column('end_time', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_game_name'), 'game', ['name'], unique=True)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=False),
sa.Column('auth_hash', sa.String(length=32), nullable=True),
sa.Column('password_hash', sa.String(length=128), nullable=True),
sa.Column('last_login', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('auth_hash'),
sa.UniqueConstraint('name')
)
op.create_table('game_player',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('game_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('role', sa.Enum('none', 'owner', 'hunter', 'bunny', name='role'), server_default='none', nullable=False),
sa.ForeignKeyConstraint(['game_id'], ['game.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('game_id', 'user_id', name='_game_user_uc')
)
op.create_table('location',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('longitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=False),
sa.Column('latitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('notification',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('game_id', sa.Integer(), nullable=False),
sa.Column('message', sa.Text(), nullable=False),
sa.Column('type', sa.String(length=64), nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['game_id'], ['game.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('objective',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=64), nullable=True),
sa.Column('game_id', sa.Integer(), 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('latitude', sa.Numeric(precision=15, scale=10, asdecimal=False), nullable=True),
sa.ForeignKeyConstraint(['game_id'], ['game.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('hash')
)
op.create_table('notification_player',
sa.Column('notification_id', sa.Integer(), nullable=False),
sa.Column('game_player_id', sa.Integer(), nullable=False),
sa.Column('been_shown', sa.Boolean(), server_default='True', nullable=False),
sa.ForeignKeyConstraint(['game_player_id'], ['game_player.id'], ),
sa.ForeignKeyConstraint(['notification_id'], ['notification.id'], ),
sa.PrimaryKeyConstraint('notification_id', 'game_player_id')
)
op.create_table('player_caught_player',
sa.Column('id', sa.Integer(), server_default='-1', autoincrement=True, nullable=False),
sa.Column('catching_player_id', sa.Integer(), nullable=False),
sa.Column('caught_player_id', sa.Integer(), nullable=False),
sa.Column('photo_reference', sa.String(length=128), nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['catching_player_id'], ['game_player.id'], ),
sa.ForeignKeyConstraint(['caught_player_id'], ['game_player.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('photo_reference')
)
op.create_table('player_found_objective',
sa.Column('objective_id', sa.Integer(), server_default='-1', nullable=False),
sa.Column('game_player_id', sa.Integer(), server_default='-1', nullable=False),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['game_player_id'], ['game_player.id'], ),
sa.ForeignKeyConstraint(['objective_id'], ['objective.id'], ),
sa.PrimaryKeyConstraint('objective_id', 'game_player_id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('player_found_objective')
op.drop_table('player_caught_player')
op.drop_table('notification_player')
op.drop_table('objective')
op.drop_table('notification')
op.drop_table('location')
op.drop_table('game_player')
op.drop_table('user')
op.drop_index(op.f('ix_game_name'), table_name='game')
op.drop_table('game')
# ### end Alembic commands ###

BIN
uploads/TestGame/caught_bunny_photos/20200719225209_Rogier_caught_Emma

Binary file not shown.

Before

Width:  |  Height:  |  Size: 582 KiB

Loading…
Cancel
Save