From 9fc875da44f21acb75fbcfe5f300df9b01e304d1 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:00:30 +0200 Subject: [PATCH 01/23] Set Ember CLI "proxy" default to "http://localhost:5000/" "ember serve" will automatically proxy API requests to the API server now. "ember serve --proxy=https://skylines.aero/" can be used to develop against the production server. --- ember/.ember-cli | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ember/.ember-cli b/ember/.ember-cli index 6f52478f37..644f89c031 100644 --- a/ember/.ember-cli +++ b/ember/.ember-cli @@ -6,5 +6,6 @@ Setting `disableAnalytics` to true will prevent any data from being sent. */ "disableAnalytics": false, - "outputPath": "../skylines/frontend/static" + "outputPath": "../skylines/frontend/static", + "proxy": "http://localhost:5000/" } From 4da01470bf23c71aa179a03930c3547e03aa4599 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:07:26 +0200 Subject: [PATCH 02/23] views/login: Remove "login" route This can be handled by the assets blueprint --- skylines/frontend/views/login.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/skylines/frontend/views/login.py b/skylines/frontend/views/login.py index 414b89da0c..1f926ef79d 100644 --- a/skylines/frontend/views/login.py +++ b/skylines/frontend/views/login.py @@ -3,7 +3,6 @@ from flask import request, g, jsonify from flask.ext.login import login_user, logout_user, current_user -from skylines.frontend.ember import send_index from skylines.model import User from skylines.schemas import CurrentUserSchema, ValidationError @@ -37,10 +36,6 @@ def inject_current_user(): else: g.current_user = current_user - @app.route('/login') - def login(): - return send_index() - @app.route('/session', methods=('PUT',)) def create_session(): json = request.get_json() From c4f571b650c342b5a7e570caff06d605091b475e Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:14:57 +0200 Subject: [PATCH 03/23] routes/login: Redirect to "index" route after login on login page --- ember/app/routes/application.js | 3 +++ ember/app/routes/login.js | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ember/app/routes/application.js b/ember/app/routes/application.js index 54801774af..f5cef1fb6b 100644 --- a/ember/app/routes/application.js +++ b/ember/app/routes/application.js @@ -56,10 +56,13 @@ export default Ember.Route.extend(ApplicationRouteMixin, { sessionAuthenticated() { const attemptedTransition = this.get('session.attemptedTransition'); + const inLoginRoute = this.controllerFor('application').get('inLoginRoute'); if (attemptedTransition) { attemptedTransition.retry(); this.set('session.attemptedTransition', null); + } else if (inLoginRoute) { + this.transitionTo('index'); } }, }); diff --git a/ember/app/routes/login.js b/ember/app/routes/login.js index f81116f6c4..91e47d152f 100644 --- a/ember/app/routes/login.js +++ b/ember/app/routes/login.js @@ -1,4 +1,14 @@ import Ember from 'ember'; import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin'; -export default Ember.Route.extend(UnauthenticatedRouteMixin); +export default Ember.Route.extend(UnauthenticatedRouteMixin, { + setupController() { + this._super(...arguments); + this.controllerFor('application').set('inLoginRoute', true); + }, + + resetController() { + this._super(...arguments); + this.controllerFor('application').set('inLoginRoute', false); + }, +}); From 5d7ac973edbef5d3e4a9474fc4f96d682648e700 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:43:18 +0200 Subject: [PATCH 04/23] api/views/mapitems: Move into "frontend" package ... which is the actual API now ... --- ember/app/utils/map-click-handler.js | 2 +- skylines/api/views/__init__.py | 2 -- skylines/frontend/views/__init__.py | 2 ++ skylines/{api => frontend}/views/mapitems.py | 10 ++++------ 4 files changed, 7 insertions(+), 9 deletions(-) rename skylines/{api => frontend}/views/mapitems.py (56%) diff --git a/ember/app/utils/map-click-handler.js b/ember/app/utils/map-click-handler.js index e5473c3477..88be7a6603 100644 --- a/ember/app/utils/map-click-handler.js +++ b/ember/app/utils/map-click-handler.js @@ -242,7 +242,7 @@ const MapClickHandler = Ember.Object.extend({ * @param {Number} lat Latitude. */ getLocationInfo(lon, lat) { - let req = $.ajax(`/api/v0/mapitems?lon=${lon}&lat=${lat}`); + let req = $.ajax(`/api/mapitems?lon=${lon}&lat=${lat}`); req.done(data => this.showLocationData(data)); req.fail(() => this.showLocationData(null)); }, diff --git a/skylines/api/views/__init__.py b/skylines/api/views/__init__.py index 67eb183f01..fd1809914e 100644 --- a/skylines/api/views/__init__.py +++ b/skylines/api/views/__init__.py @@ -16,7 +16,6 @@ def register(app): from .airports import airports_blueprint from .airspace import airspace_blueprint from .clubs import clubs_blueprint - from .mapitems import mapitems_blueprint from .search import search_blueprint from .users import users from .user import user @@ -42,7 +41,6 @@ def require_user_agent(): app.register_blueprint(airports_blueprint) app.register_blueprint(airspace_blueprint) app.register_blueprint(clubs_blueprint) - app.register_blueprint(mapitems_blueprint) app.register_blueprint(search_blueprint) app.register_blueprint(user) app.register_blueprint(users) diff --git a/skylines/frontend/views/__init__.py b/skylines/frontend/views/__init__.py index 7b95e1e7ac..4b1cfd198b 100644 --- a/skylines/frontend/views/__init__.py +++ b/skylines/frontend/views/__init__.py @@ -12,6 +12,7 @@ from .flight import flight_blueprint from .flights import flights_blueprint from .livetrack24 import lt24_blueprint +from .mapitems import mapitems_blueprint from .notifications import notifications_blueprint from .ranking import ranking_blueprint from .search import search_blueprint @@ -41,6 +42,7 @@ def register(app): app.register_blueprint(flight_blueprint) app.register_blueprint(flights_blueprint) app.register_blueprint(lt24_blueprint) + app.register_blueprint(mapitems_blueprint) app.register_blueprint(notifications_blueprint) app.register_blueprint(ranking_blueprint) app.register_blueprint(search_blueprint) diff --git a/skylines/api/views/mapitems.py b/skylines/frontend/views/mapitems.py similarity index 56% rename from skylines/api/views/mapitems.py rename to skylines/frontend/views/mapitems.py index 7b209d32b3..0b738d37ac 100644 --- a/skylines/api/views/mapitems.py +++ b/skylines/frontend/views/mapitems.py @@ -1,15 +1,13 @@ -from flask import Blueprint, request +from flask import Blueprint, request, jsonify from skylines import api -from .json import jsonify -from .parser import parse_location +from skylines.api.views.parser import parse_location mapitems_blueprint = Blueprint('mapitems', 'skylines') -@mapitems_blueprint.route('/mapitems/') -@mapitems_blueprint.route('/mapitems', endpoint='list') -def _list(): +@mapitems_blueprint.route('/api/mapitems', strict_slashes=False) +def list(): location = parse_location(request.args) return jsonify({ 'airspaces': api.get_airspaces_by_location(location), From cfd874a537a4ec73b5f3bb596b952345e909529f Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:45:19 +0200 Subject: [PATCH 05/23] api/views: Use strict_slashes=False parameter for routes --- skylines/api/views/airports.py | 5 ++--- skylines/api/views/airspace.py | 5 ++--- skylines/api/views/user.py | 3 +-- skylines/api/views/users.py | 5 ++--- skylines/api/views/waves.py | 5 ++--- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/skylines/api/views/airports.py b/skylines/api/views/airports.py index 743a11cd40..89dbf9197a 100644 --- a/skylines/api/views/airports.py +++ b/skylines/api/views/airports.py @@ -12,10 +12,9 @@ } -@airports_blueprint.route('/airports/') -@airports_blueprint.route('/airports', endpoint='list') +@airports_blueprint.route('/airports', strict_slashes=False) @use_args(bbox_args) -def _list(args): +def list(args): airports = api.get_airports_by_bbox(args['bbox']) return jsonify(airports) diff --git a/skylines/api/views/airspace.py b/skylines/api/views/airspace.py index bbf62f1a4b..3ac6085b47 100644 --- a/skylines/api/views/airspace.py +++ b/skylines/api/views/airspace.py @@ -7,8 +7,7 @@ airspace_blueprint = Blueprint('airspace', 'skylines') -@airspace_blueprint.route('/airspace/') -@airspace_blueprint.route('/airspace', endpoint='list') -def _list(): +@airspace_blueprint.route('/airspace', strict_slashes=False) +def list(): location = parse_location(request.args) return jsonify(api.get_airspaces_by_location(location)) diff --git a/skylines/api/views/user.py b/skylines/api/views/user.py index ffc5d79da2..4cc2b91c60 100644 --- a/skylines/api/views/user.py +++ b/skylines/api/views/user.py @@ -8,8 +8,7 @@ user = Blueprint('user', 'skylines') -@user.route('/user/') -@user.route('/user') +@user.route('/user', strict_slashes=False) @oauth.required() def read(): user = User.get(request.user_id) diff --git a/skylines/api/views/users.py b/skylines/api/views/users.py index f57bc33e81..e1cd52659d 100644 --- a/skylines/api/views/users.py +++ b/skylines/api/views/users.py @@ -10,10 +10,9 @@ users = Blueprint('users', 'skylines') -@users.route('/users/') -@users.route('/users', endpoint='list') +@users.route('/users', strict_slashes=False) @use_args(pagination_args) -def _list(args): +def list(args): offset = (args['page'] - 1) * args['per_page'] limit = args['per_page'] diff --git a/skylines/api/views/waves.py b/skylines/api/views/waves.py index a1ac0e9af5..b8db41721d 100644 --- a/skylines/api/views/waves.py +++ b/skylines/api/views/waves.py @@ -7,8 +7,7 @@ waves_blueprint = Blueprint('waves', 'skylines') -@waves_blueprint.route('/mountain_wave_project/') -@waves_blueprint.route('/mountain_wave_project', endpoint='list') -def _list(): +@waves_blueprint.route('/mountain_wave_project', strict_slashes=False) +def list(): location = parse_location(request.args) return jsonify(api.get_waves_by_location(location)) From b04152a39a2734c5f6ed22720b2f3984518be2d5 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:55:44 +0200 Subject: [PATCH 06/23] views/club: Remove URL processors --- skylines/frontend/views/club.py | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/skylines/frontend/views/club.py b/skylines/frontend/views/club.py index 71d45fc65a..13a804c262 100644 --- a/skylines/frontend/views/club.py +++ b/skylines/frontend/views/club.py @@ -8,28 +8,20 @@ club_blueprint = Blueprint('club', 'skylines') -@club_blueprint.url_value_preprocessor -def _pull_user_id(endpoint, values): - g.club_id = values.pop('club_id') - g.club = get_requested_record(Club, g.club_id) - - -@club_blueprint.url_defaults -def _add_user_id(endpoint, values): - if hasattr(g, 'club_id'): - values.setdefault('club_id', g.club_id) - - @club_blueprint.route('/api/clubs/', strict_slashes=False) -def read(): - json = ClubSchema().dump(g.club).data - json['isWritable'] = g.club.is_writable(g.current_user) +def read(club_id): + club = get_requested_record(Club, club_id) + + json = ClubSchema().dump(club).data + json['isWritable'] = club.is_writable(g.current_user) return jsonify(**json) @club_blueprint.route('/api/clubs/', methods=['POST'], strict_slashes=False) -def update(): +def update(club_id): + club = get_requested_record(Club, club_id) + json = request.get_json() if json is None: return jsonify(error='invalid-request'), 400 @@ -42,13 +34,13 @@ def update(): if 'name' in data: name = data.get('name') - if name != g.club.name and Club.exists(name=name): + if name != club.name and Club.exists(name=name): return jsonify(error='duplicate-club-name'), 422 - g.club.name = name + club.name = name if 'website' in data: - g.club.website = data.get('website') + club.website = data.get('website') db.session.commit() From 87574d65c7cd6971be32a29b56a96f685c8fc737 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 18:56:45 +0200 Subject: [PATCH 07/23] views/flight: Remove URL processors --- skylines/frontend/views/flight.py | 147 +++++++++++++++--------------- 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/skylines/frontend/views/flight.py b/skylines/frontend/views/flight.py index 1a90b06198..187d4dd3b6 100644 --- a/skylines/frontend/views/flight.py +++ b/skylines/frontend/views/flight.py @@ -10,7 +10,7 @@ from skylines.database import db from skylines.lib import files -from skylines.lib.dbutil import get_requested_record_list +from skylines.lib.dbutil import get_requested_record from skylines.lib.xcsoar_ import analyse_flight from skylines.lib.datetime import from_seconds_of_day from skylines.lib.geo import METERS_PER_DEGREE @@ -42,31 +42,10 @@ def _reanalyse_if_needed(flight): db.session.commit() -@flight_blueprint.url_value_preprocessor -def _pull_flight_id(endpoint, values): - g.flight_id = values.pop('flight_id') - - def _patch_query(q): return q.join(Flight.igc_file) \ - .options(contains_eager(Flight.igc_file)) \ - .filter(Flight.is_viewable(g.current_user)) - - -@flight_blueprint.before_request -def _query_flights(): - flights = get_requested_record_list( - Flight, g.flight_id, patch_query=_patch_query) - - g.flight = flights[0] - - map(_reanalyse_if_needed, flights) - - -@flight_blueprint.url_defaults -def _add_flight_id(endpoint, values): - if hasattr(g, 'flight_id'): - values.setdefault('flight_id', g.flight_id) + .options(contains_eager(Flight.igc_file)) \ + .filter(Flight.is_viewable(g.current_user)) def _get_flight_path(flight, threshold=0.001, max_points=3000): @@ -171,18 +150,24 @@ class NearFlightSchema(Schema): @flight_blueprint.route('/api/flights/', strict_slashes=False) -def read(): - mark_flight_notifications_read(g.flight) +def read(flight_id): + flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) + + if not flight.is_viewable(g.current_user): + return jsonify(), 404 + + _reanalyse_if_needed(flight) + mark_flight_notifications_read(flight) - flight = FlightSchema().dump(g.flight).data + flight_json = FlightSchema().dump(flight).data if 'extended' not in request.args: - return jsonify(flight=flight) + return jsonify(flight=flight_json) - near_flights = FlightMeetings.get_meetings(g.flight).values() + near_flights = FlightMeetings.get_meetings(flight).values() near_flights = NearFlightSchema().dump(near_flights, many=True).data - comments = FlightCommentSchema().dump(g.flight.comments, many=True).data + comments = FlightCommentSchema().dump(flight.comments, many=True).data phases_schema = FlightPhaseSchema(only=( 'circlingDirection', @@ -197,7 +182,7 @@ def read(): 'glideRate', )) - phases = phases_schema.dump(g.flight.phases, many=True).data + phases = phases_schema.dump(flight.phases, many=True).data cruise_performance_schema = FlightPhaseSchema(only=( 'duration', @@ -210,7 +195,7 @@ def read(): 'count', )) - cruise_performance = cruise_performance_schema.dump(g.flight.cruise_performance).data + cruise_performance = cruise_performance_schema.dump(flight.cruise_performance).data circling_performance_schema = FlightPhaseSchema(only=( 'circlingDirection', @@ -221,17 +206,17 @@ def read(): 'altDiff', )) - circling_performance = circling_performance_schema.dump(g.flight.circling_performance, many=True).data + circling_performance = circling_performance_schema.dump(flight.circling_performance, many=True).data performance = dict(circling=circling_performance, cruise=cruise_performance) contest_leg_schema = ContestLegSchema() contest_legs = {} for type in ['classic', 'triangle']: - legs = g.flight.get_contest_legs('olc_plus', type) + legs = flight.get_contest_legs('olc_plus', type) contest_legs[type] = contest_leg_schema.dump(legs, many=True).data return jsonify( - flight=flight, + flight=flight_json, near_flights=near_flights, comments=comments, contest_legs=contest_legs, @@ -240,21 +225,26 @@ def read(): @flight_blueprint.route('/api/flights//json') -def json(): +def json(flight_id): + flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) + + if not flight.is_viewable(g.current_user): + return jsonify(), 404 + # Return HTTP Status code 304 if an upstream or browser cache already # contains the response and if the igc file did not change to reduce # latency and server load # This implementation is very basic. Sadly Flask (0.10.1) does not have # this feature - last_modified = g.flight.time_modified \ + last_modified = flight.time_modified \ .strftime('%a, %d %b %Y %H:%M:%S GMT') modified_since = request.headers.get('If-Modified-Since') etag = request.headers.get('If-None-Match') if (modified_since and modified_since == last_modified) or \ - (etag and etag == g.flight.igc_file.md5): + (etag and etag == flight.igc_file.md5): return ('', 304) - trace = _get_flight_path(g.flight, threshold=0.0001, max_points=10000) + trace = _get_flight_path(flight, threshold=0.0001, max_points=10000) if not trace: abort(404) @@ -266,14 +256,14 @@ def json(): contests=trace['contests'], elevations_t=trace['elevations_t'], elevations_h=trace['elevations_h'], - sfid=g.flight.id, + sfid=flight.id, geoid=trace['geoid'], additional=dict( - registration=g.flight.registration, - competition_id=g.flight.competition_id))) + registration=flight.registration, + competition_id=flight.competition_id))) resp.headers['Last-Modified'] = last_modified - resp.headers['Etag'] = g.flight.igc_file.md5 + resp.headers['Etag'] = flight.igc_file.md5 return resp @@ -338,7 +328,12 @@ def _get_near_flights(flight, location, time, max_distance=1000): @flight_blueprint.route('/api/flights//near') -def near(): +def near(flight_id): + flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) + + if not flight.is_viewable(g.current_user): + return jsonify(), 404 + try: latitude = float(request.args['lat']) longitude = float(request.args['lon']) @@ -348,9 +343,9 @@ def near(): abort(400) location = Location(latitude=latitude, longitude=longitude) - time = from_seconds_of_day(g.flight.takeoff_time, time) + time = from_seconds_of_day(flight.takeoff_time, time) - flights = _get_near_flights(g.flight, location, time, 1000) + flights = _get_near_flights(flight, location, time, 1000) def add_flight_path(flight): trace = _get_flight_path(flight, threshold=0.0001, max_points=10000) @@ -364,8 +359,10 @@ def add_flight_path(flight): @flight_blueprint.route('/api/flights/', methods=['POST'], strict_slashes=False) -def update(): - if not g.flight.is_writable(g.current_user): +def update(flight_id): + flight = get_requested_record(Flight, flight_id) + + if not flight.is_writable(g.current_user): return jsonify(), 403 json = request.get_json() @@ -390,18 +387,18 @@ def update(): if pilot_club_id != g.current_user.club_id or (pilot_club_id is None and pilot_id != g.current_user.id): return jsonify(error='pilot-disallowed'), 422 - if g.flight.pilot_id != pilot_id: - g.flight.pilot_id = pilot_id + if flight.pilot_id != pilot_id: + flight.pilot_id = pilot_id # pilot_name is irrelevant, if pilot_id is given - g.flight.pilot_name = None + flight.pilot_name = None # update club if pilot changed - g.flight.club_id = pilot_club_id + flight.club_id = pilot_club_id else: - g.flight.pilot_id = None + flight.pilot_id = None if 'pilot_name' in data: - g.flight.pilot_name = data['pilot_name'] + flight.pilot_name = data['pilot_name'] if 'co_pilot_id' in data: co_pilot_id = data['co_pilot_id'] @@ -417,17 +414,17 @@ def update(): or (co_pilot_club_id is None and co_pilot_id != g.current_user.id): return jsonify(error='co-pilot-disallowed'), 422 - g.flight.co_pilot_id = co_pilot_id + flight.co_pilot_id = co_pilot_id # co_pilot_name is irrelevant, if co_pilot_id is given - g.flight.co_pilot_name = None + flight.co_pilot_name = None else: - g.flight.co_pilot_id = None + flight.co_pilot_id = None if 'co_pilot_name' in data: - g.flight.co_pilot_name = data['co_pilot_name'] + flight.co_pilot_name = data['co_pilot_name'] - if g.flight.co_pilot_id is not None and g.flight.co_pilot_id == g.flight.pilot_id: + if flight.co_pilot_id is not None and flight.co_pilot_id == flight.pilot_id: return jsonify(error='copilot-equals-pilot'), 422 if 'model_id' in data: @@ -436,44 +433,48 @@ def update(): if model_id is not None and not AircraftModel.exists(id=model_id): return jsonify(error='unknown-aircraft-model'), 422 - g.flight.model_id = model_id + flight.model_id = model_id if 'registration' in data: - g.flight.registration = data['registration'] + flight.registration = data['registration'] if 'competition_id' in data: - g.flight.competition_id = data['competition_id'] + flight.competition_id = data['competition_id'] if 'privacy_level' in data: - g.flight.privacy_level = data['privacy_level'] + flight.privacy_level = data['privacy_level'] try: - tasks.analyse_flight.delay(g.flight.id) - tasks.find_meetings.delay(g.flight.id) + tasks.analyse_flight.delay(flight.id) + tasks.find_meetings.delay(flight.id) except ConnectionError: current_app.logger.info('Cannot connect to Redis server') - g.flight.time_modified = datetime.utcnow() + flight.time_modified = datetime.utcnow() db.session.commit() return jsonify() @flight_blueprint.route('/api/flights/', methods=('DELETE',), strict_slashes=False) -def delete(): - if not g.flight.is_writable(g.current_user): +def delete(flight_id): + flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) + + if not flight.is_writable(g.current_user): abort(403) - files.delete_file(g.flight.igc_file.filename) - db.session.delete(g.flight) - db.session.delete(g.flight.igc_file) + files.delete_file(flight.igc_file.filename) + db.session.delete(flight) + db.session.delete(flight.igc_file) db.session.commit() return jsonify() @flight_blueprint.route('/api/flights//comments', methods=('POST',)) -def add_comment(): +def add_comment(flight_id): + flight = get_requested_record(Flight, flight_id) + if not g.current_user: return jsonify(), 403 @@ -488,7 +489,7 @@ def add_comment(): comment = FlightComment() comment.user = g.current_user - comment.flight = g.flight + comment.flight = flight comment.text = data['text'] create_flight_comment_notifications(comment) From d67702a29393c8b178ddc40440fc3262f062afdd Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 19:04:10 +0200 Subject: [PATCH 08/23] views/track: Remove URL processors --- skylines/frontend/views/track.py | 48 +++++++++++--------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/skylines/frontend/views/track.py b/skylines/frontend/views/track.py index 33f3a34f1f..fe07a52171 100644 --- a/skylines/frontend/views/track.py +++ b/skylines/frontend/views/track.py @@ -1,10 +1,10 @@ from datetime import datetime, timedelta from math import log -from flask import Blueprint, request, abort, jsonify, g +from flask import Blueprint, request, abort, jsonify from sqlalchemy.sql.expression import and_ -from skylines.lib.dbutil import get_requested_record_list +from skylines.lib.dbutil import get_requested_record_list, get_requested_record from skylines.lib.helpers import color from skylines.lib.xcsoar_ import FlightPathFix from skylines.lib.geoid import egm96_height @@ -14,28 +14,6 @@ track_blueprint = Blueprint('track', 'skylines') - -@track_blueprint.url_value_preprocessor -def _pull_user_id(endpoint, values): - if request.endpoint == 'track.html': - return - - g.user_id = values.pop('user_id') - - g.pilots = get_requested_record_list( - User, g.user_id, joinedload=[User.club]) - - color_gen = color.generator() - for pilot in g.pilots: - pilot.color = color_gen.next() - - -@track_blueprint.url_defaults -def _add_user_id(endpoint, values): - if hasattr(g, 'user_id'): - values.setdefault('user_id', g.user_id) - - UNKNOWN_ELEVATION = -1000 @@ -118,24 +96,30 @@ def _get_flight_path(pilot, threshold=0.001, last_update=None): # Use `live` alias here since `/api/tracking/*` is filtered by the "EasyPrivacy" adblocker list... -@track_blueprint.route('/api/tracking/', strict_slashes=False) -@track_blueprint.route('/api/live/', strict_slashes=False) -def read(): - traces = map(_get_flight_path, g.pilots) +@track_blueprint.route('/api/tracking/', strict_slashes=False) +@track_blueprint.route('/api/live/', strict_slashes=False) +def read(user_ids): + pilots = get_requested_record_list(User, user_ids, joinedload=[User.club]) + + color_gen = color.generator() + for pilot in pilots: + pilot.color = color_gen.next() + + traces = map(_get_flight_path, pilots) if not any(traces): traces = None user_schema = UserSchema() pilots_json = [] - for pilot in g.pilots: + for pilot in pilots: json = user_schema.dump(pilot).data json['color'] = pilot.color pilots_json.append(json) flights = [] if traces: - for pilot, trace in zip(g.pilots, traces): + for pilot, trace in zip(pilots, traces): if trace: flights.append({ 'sfid': pilot.id, @@ -158,8 +142,8 @@ def read(): @track_blueprint.route('/api/tracking//json') @track_blueprint.route('/api/live//json') -def json(): - pilot = g.pilots[0] +def json(user_id): + pilot = get_requested_record(User, user_id, joinedload=[User.club]) last_update = request.values.get('last_update', 0, type=int) trace = _get_flight_path(pilot, threshold=0.001, last_update=last_update) From a7c0edb4f038a4711a1eadf2df208c2e68c65318 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 19:22:47 +0200 Subject: [PATCH 09/23] views/user: Remove URL processors --- skylines/frontend/views/user.py | 67 +++++++++++++++------------------ 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/skylines/frontend/views/user.py b/skylines/frontend/views/user.py index 71b54ab6a6..15edee9611 100644 --- a/skylines/frontend/views/user.py +++ b/skylines/frontend/views/user.py @@ -17,21 +17,6 @@ user_blueprint = Blueprint('user', 'skylines') -@user_blueprint.url_value_preprocessor -def _pull_user_id(endpoint, values): - if request.endpoint == 'user.html': - return - - g.user_id = values.pop('user_id') - g.user = get_requested_record(User, g.user_id) - - -@user_blueprint.url_defaults -def _add_user_id(endpoint, values): - if hasattr(g, 'user_id'): - values.setdefault('user_id', g.user_id) - - def _largest_flight(user, schema): flight = user.get_largest_flights() \ .filter(Flight.is_rankable()) \ @@ -73,11 +58,11 @@ class QuickStatsSchema(Schema): duration = fields.TimeDelta() -def _quick_stats(): +def _quick_stats(user): result = db.session.query(func.count('*').label('flights'), func.sum(Flight.olc_classic_distance).label('distance'), func.sum(Flight.duration).label('duration')) \ - .filter(Flight.pilot == g.user) \ + .filter(Flight.pilot == user) \ .filter(Flight.date_local > (date.today() - timedelta(days=365))) \ .filter(Flight.is_rankable()) \ .one() @@ -85,9 +70,9 @@ def _quick_stats(): return QuickStatsSchema().dump(result).data -def _get_takeoff_locations(): +def _get_takeoff_locations(user): locations = Location.get_clustered_locations( - Flight.takeoff_location_wkt, filter=and_(Flight.pilot == g.user, Flight.is_rankable())) + Flight.takeoff_location_wkt, filter=and_(Flight.pilot == user, Flight.is_rankable())) return [loc.to_lonlat() for loc in locations] @@ -104,27 +89,31 @@ def add_user_filter(query): @user_blueprint.route('/api/users/', strict_slashes=False) -def read(): - user_schema = CurrentUserSchema() if g.user == g.current_user else UserSchema() - user = user_schema.dump(g.user).data +def read(user_id): + user = get_requested_record(User, user_id) + + user_schema = CurrentUserSchema() if user == g.current_user else UserSchema() + user_json = user_schema.dump(user).data if g.current_user: - user['followed'] = g.current_user.follows(g.user) + user_json['followed'] = g.current_user.follows(user) if 'extended' in request.args: - user['distanceFlights'] = _distance_flights(g.user) - user['stats'] = _quick_stats() - user['takeoffLocations'] = _get_takeoff_locations() + user_json['distanceFlights'] = _distance_flights(user) + user_json['stats'] = _quick_stats(user) + user_json['takeoffLocations'] = _get_takeoff_locations(user) - mark_user_notifications_read(g.user) + mark_user_notifications_read(user) - return jsonify(**user) + return jsonify(**user_json) @user_blueprint.route('/api/users//followers') -def followers(): +def followers(user_id): + user = get_requested_record(User, user_id) + # Query list of pilots that are following the selected user - query = Follower.query(destination=g.user) \ + query = Follower.query(destination=user) \ .join('source') \ .options(contains_eager('source')) \ .options(subqueryload('source.club')) \ @@ -139,9 +128,11 @@ def followers(): @user_blueprint.route('/api/users//following') -def following(): +def following(user_id): + user = get_requested_record(User, user_id) + # Query list of pilots that are following the selected user - query = Follower.query(source=g.user) \ + query = Follower.query(source=user) \ .join('destination') \ .options(contains_eager('destination')) \ .options(subqueryload('destination.club')) \ @@ -176,16 +167,18 @@ def add_current_user_follows(followers): @user_blueprint.route('/api/users//follow') @login_required -def follow(): - Follower.follow(g.current_user, g.user) - create_follower_notification(g.user, g.current_user) +def follow(user_id): + user = get_requested_record(User, user_id) + Follower.follow(g.current_user, user) + create_follower_notification(user, g.current_user) db.session.commit() return jsonify() @user_blueprint.route('/api/users//unfollow') @login_required -def unfollow(): - Follower.unfollow(g.current_user, g.user) +def unfollow(user_id): + user = get_requested_record(User, user_id) + Follower.unfollow(g.current_user, user) db.session.commit() return jsonify() From 005bded46d77be51028819aab5cc42eac11dffd1 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 19:24:20 +0200 Subject: [PATCH 10/23] views: Use "/api" url_prefixes --- skylines/frontend/views/__init__.py | 38 +++++++++++----------- skylines/frontend/views/about.py | 6 ++-- skylines/frontend/views/aircraft_models.py | 2 +- skylines/frontend/views/airport.py | 2 +- skylines/frontend/views/club.py | 4 +-- skylines/frontend/views/clubs.py | 2 +- skylines/frontend/views/flight.py | 12 +++---- skylines/frontend/views/flights.py | 16 ++++----- skylines/frontend/views/mapitems.py | 2 +- skylines/frontend/views/notifications.py | 4 +-- skylines/frontend/views/ranking.py | 6 ++-- skylines/frontend/views/search.py | 2 +- skylines/frontend/views/settings.py | 10 +++--- skylines/frontend/views/statistics.py | 4 +-- skylines/frontend/views/timeline.py | 2 +- skylines/frontend/views/track.py | 8 ++--- skylines/frontend/views/tracking.py | 6 ++-- skylines/frontend/views/upload.py | 8 ++--- skylines/frontend/views/user.py | 10 +++--- skylines/frontend/views/users.py | 8 ++--- 20 files changed, 76 insertions(+), 76 deletions(-) diff --git a/skylines/frontend/views/__init__.py b/skylines/frontend/views/__init__.py index 4b1cfd198b..68466473e8 100644 --- a/skylines/frontend/views/__init__.py +++ b/skylines/frontend/views/__init__.py @@ -32,26 +32,26 @@ def register(app): register_i18n(app) register_login(app) - app.register_blueprint(about_blueprint) - app.register_blueprint(airport_blueprint) - app.register_blueprint(aircraft_models_blueprint) + app.register_blueprint(about_blueprint, url_prefix='/api') + app.register_blueprint(airport_blueprint, url_prefix='/api') + app.register_blueprint(aircraft_models_blueprint, url_prefix='/api') app.register_blueprint(assets_blueprint) - app.register_blueprint(club_blueprint) - app.register_blueprint(clubs_blueprint) + app.register_blueprint(club_blueprint, url_prefix='/api') + app.register_blueprint(clubs_blueprint, url_prefix='/api') app.register_blueprint(files_blueprint) - app.register_blueprint(flight_blueprint) - app.register_blueprint(flights_blueprint) + app.register_blueprint(flight_blueprint, url_prefix='/api') + app.register_blueprint(flights_blueprint, url_prefix='/api') app.register_blueprint(lt24_blueprint) - app.register_blueprint(mapitems_blueprint) - app.register_blueprint(notifications_blueprint) - app.register_blueprint(ranking_blueprint) - app.register_blueprint(search_blueprint) - app.register_blueprint(settings_blueprint) - app.register_blueprint(statistics_blueprint) - app.register_blueprint(timeline_blueprint) - app.register_blueprint(track_blueprint) - app.register_blueprint(tracking_blueprint) - app.register_blueprint(upload_blueprint) - app.register_blueprint(user_blueprint) - app.register_blueprint(users_blueprint) + app.register_blueprint(mapitems_blueprint, url_prefix='/api') + app.register_blueprint(notifications_blueprint, url_prefix='/api') + app.register_blueprint(ranking_blueprint, url_prefix='/api') + app.register_blueprint(search_blueprint, url_prefix='/api') + app.register_blueprint(settings_blueprint, url_prefix='/api') + app.register_blueprint(statistics_blueprint, url_prefix='/api') + app.register_blueprint(timeline_blueprint, url_prefix='/api') + app.register_blueprint(track_blueprint, url_prefix='/api') + app.register_blueprint(tracking_blueprint, url_prefix='/api') + app.register_blueprint(upload_blueprint, url_prefix='/api') + app.register_blueprint(user_blueprint, url_prefix='/api') + app.register_blueprint(users_blueprint, url_prefix='/api') app.register_blueprint(widgets_blueprint) diff --git a/skylines/frontend/views/about.py b/skylines/frontend/views/about.py index 24ccff728f..0091f0aebf 100644 --- a/skylines/frontend/views/about.py +++ b/skylines/frontend/views/about.py @@ -5,7 +5,7 @@ about_blueprint = Blueprint('about', 'skylines') -@about_blueprint.route('/api/imprint') +@about_blueprint.route('/imprint') def imprint(): content = current_app.config.get( 'SKYLINES_IMPRINT', @@ -14,7 +14,7 @@ def imprint(): return jsonify(content=content) -@about_blueprint.route('/api/team') +@about_blueprint.route('/team') def skylines_team(): path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..', 'AUTHORS.md') @@ -24,7 +24,7 @@ def skylines_team(): return jsonify(content=content) -@about_blueprint.route('/api/license') +@about_blueprint.route('/license') def license(): path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..', 'LICENSE') diff --git a/skylines/frontend/views/aircraft_models.py b/skylines/frontend/views/aircraft_models.py index 4648981cc2..7f79acd72f 100644 --- a/skylines/frontend/views/aircraft_models.py +++ b/skylines/frontend/views/aircraft_models.py @@ -6,7 +6,7 @@ aircraft_models_blueprint = Blueprint('aircraft_models', 'skylines') -@aircraft_models_blueprint.route('/api/aircraft-models', strict_slashes=False) +@aircraft_models_blueprint.route('/aircraft-models', strict_slashes=False) def index(): models = AircraftModel.query() \ .order_by(AircraftModel.kind) \ diff --git a/skylines/frontend/views/airport.py b/skylines/frontend/views/airport.py index 27ba3221a8..d2a96c8902 100644 --- a/skylines/frontend/views/airport.py +++ b/skylines/frontend/views/airport.py @@ -6,7 +6,7 @@ airport_blueprint = Blueprint('airport', 'skylines') -@airport_blueprint.route('/api/airports/') +@airport_blueprint.route('/airports/') def index(airport_id): airport = get_requested_record(Airport, airport_id) diff --git a/skylines/frontend/views/club.py b/skylines/frontend/views/club.py index 13a804c262..ae2b448f3b 100644 --- a/skylines/frontend/views/club.py +++ b/skylines/frontend/views/club.py @@ -8,7 +8,7 @@ club_blueprint = Blueprint('club', 'skylines') -@club_blueprint.route('/api/clubs/', strict_slashes=False) +@club_blueprint.route('/clubs/', strict_slashes=False) def read(club_id): club = get_requested_record(Club, club_id) @@ -18,7 +18,7 @@ def read(club_id): return jsonify(**json) -@club_blueprint.route('/api/clubs/', methods=['POST'], strict_slashes=False) +@club_blueprint.route('/clubs/', methods=['POST'], strict_slashes=False) def update(club_id): club = get_requested_record(Club, club_id) diff --git a/skylines/frontend/views/clubs.py b/skylines/frontend/views/clubs.py index 5dd8555808..e13a117930 100644 --- a/skylines/frontend/views/clubs.py +++ b/skylines/frontend/views/clubs.py @@ -7,7 +7,7 @@ clubs_blueprint = Blueprint('clubs', 'skylines') -@clubs_blueprint.route('/api/clubs/', strict_slashes=False) +@clubs_blueprint.route('/clubs/', strict_slashes=False) def list(): clubs = Club.query().order_by(func.lower(Club.name)) diff --git a/skylines/frontend/views/flight.py b/skylines/frontend/views/flight.py index 187d4dd3b6..5bae25d6c4 100644 --- a/skylines/frontend/views/flight.py +++ b/skylines/frontend/views/flight.py @@ -149,7 +149,7 @@ class NearFlightSchema(Schema): times = fields.Nested(MeetingTimeSchema, many=True) -@flight_blueprint.route('/api/flights/', strict_slashes=False) +@flight_blueprint.route('/flights/', strict_slashes=False) def read(flight_id): flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) @@ -224,7 +224,7 @@ def read(flight_id): performance=performance) -@flight_blueprint.route('/api/flights//json') +@flight_blueprint.route('/flights//json') def json(flight_id): flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) @@ -327,7 +327,7 @@ def _get_near_flights(flight, location, time, max_distance=1000): return flights -@flight_blueprint.route('/api/flights//near') +@flight_blueprint.route('/flights//near') def near(flight_id): flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) @@ -358,7 +358,7 @@ def add_flight_path(flight): return jsonify(flights=map(add_flight_path, flights)) -@flight_blueprint.route('/api/flights/', methods=['POST'], strict_slashes=False) +@flight_blueprint.route('/flights/', methods=['POST'], strict_slashes=False) def update(flight_id): flight = get_requested_record(Flight, flight_id) @@ -456,7 +456,7 @@ def update(flight_id): return jsonify() -@flight_blueprint.route('/api/flights/', methods=('DELETE',), strict_slashes=False) +@flight_blueprint.route('/flights/', methods=('DELETE',), strict_slashes=False) def delete(flight_id): flight = get_requested_record(Flight, flight_id, joinedload=[Flight.igc_file]) @@ -471,7 +471,7 @@ def delete(flight_id): return jsonify() -@flight_blueprint.route('/api/flights//comments', methods=('POST',)) +@flight_blueprint.route('/flights//comments', methods=('POST',)) def add_comment(flight_id): flight = get_requested_record(Flight, flight_id) diff --git a/skylines/frontend/views/flights.py b/skylines/frontend/views/flights.py index e823df8a74..0cbb086cec 100644 --- a/skylines/frontend/views/flights.py +++ b/skylines/frontend/views/flights.py @@ -127,12 +127,12 @@ def _create_list(date=None, pilot=None, club=None, airport=None, return jsonify(**json) -@flights_blueprint.route('/api/flights/all') +@flights_blueprint.route('/flights/all') def all(): return _create_list(default_sorting_column='date', default_sorting_order='desc') -@flights_blueprint.route('/api/flights/date/') +@flights_blueprint.route('/flights/date/') def date(date, latest=False): try: if isinstance(date, (str, unicode)): @@ -147,7 +147,7 @@ def date(date, latest=False): return _create_list(date=date, default_sorting_column='score', default_sorting_order='desc') -@flights_blueprint.route('/api/flights/latest') +@flights_blueprint.route('/flights/latest') def latest(): query = db.session \ .query(func.max(Flight.date_local).label('date')) \ @@ -162,7 +162,7 @@ def latest(): return date(date_, latest=True) -@flights_blueprint.route('/api/flights/pilot/') +@flights_blueprint.route('/flights/pilot/') def pilot(id): pilot = get_requested_record(User, id) @@ -171,21 +171,21 @@ def pilot(id): return _create_list(pilot=pilot, default_sorting_column='date', default_sorting_order='desc') -@flights_blueprint.route('/api/flights/club/') +@flights_blueprint.route('/flights/club/') def club(id): club = get_requested_record(Club, id) return _create_list(club=club, default_sorting_column='date', default_sorting_order='desc') -@flights_blueprint.route('/api/flights/airport/') +@flights_blueprint.route('/flights/airport/') def airport(id): airport = get_requested_record(Airport, id) return _create_list(airport=airport, default_sorting_column='date', default_sorting_order='desc') -@flights_blueprint.route('/api/flights/unassigned') +@flights_blueprint.route('/flights/unassigned') def unassigned(): if not g.current_user: return jsonify(), 400 @@ -196,7 +196,7 @@ def unassigned(): return _create_list(filter=f, default_sorting_column='date', default_sorting_order='desc') -@flights_blueprint.route('/api/flights/list/') +@flights_blueprint.route('/flights/list/') def list(ids): if not ids: return jsonify(), 400 diff --git a/skylines/frontend/views/mapitems.py b/skylines/frontend/views/mapitems.py index 0b738d37ac..c5a1c3d546 100644 --- a/skylines/frontend/views/mapitems.py +++ b/skylines/frontend/views/mapitems.py @@ -6,7 +6,7 @@ mapitems_blueprint = Blueprint('mapitems', 'skylines') -@mapitems_blueprint.route('/api/mapitems', strict_slashes=False) +@mapitems_blueprint.route('/mapitems', strict_slashes=False) def list(): location = parse_location(request.args) return jsonify({ diff --git a/skylines/frontend/views/notifications.py b/skylines/frontend/views/notifications.py index 4d60f27ccd..74c37a1600 100644 --- a/skylines/frontend/views/notifications.py +++ b/skylines/frontend/views/notifications.py @@ -29,7 +29,7 @@ def _filter_query(query, args): return query -@notifications_blueprint.route('/api/notifications', strict_slashes=False) +@notifications_blueprint.route('/notifications', strict_slashes=False) @login_required("You have to login to read notifications.") def list(): query = Notification.query(recipient=g.current_user) \ @@ -59,7 +59,7 @@ def get_event(notification): return jsonify(events=(map(convert_event, events))) -@notifications_blueprint.route('/api/notifications/clear', methods=('POST',)) +@notifications_blueprint.route('/notifications/clear', methods=('POST',)) @login_required("You have to login to clear notifications.") def clear(): def filter_func(query): diff --git a/skylines/frontend/views/ranking.py b/skylines/frontend/views/ranking.py index c05219846d..f7794c0793 100644 --- a/skylines/frontend/views/ranking.py +++ b/skylines/frontend/views/ranking.py @@ -69,7 +69,7 @@ def _parse_year(): return current_year -@ranking_blueprint.route('/api/ranking/pilots') +@ranking_blueprint.route('/ranking/pilots') def pilots(): data = _handle_request(User, 'pilot_id') @@ -89,7 +89,7 @@ def pilots(): return jsonify(ranking=json, total=g.paginators['result'].count) -@ranking_blueprint.route('/api/ranking/clubs') +@ranking_blueprint.route('/ranking/clubs') def clubs(): data = _handle_request(Club, 'club_id') @@ -109,7 +109,7 @@ def clubs(): return jsonify(ranking=json, total=g.paginators['result'].count) -@ranking_blueprint.route('/api/ranking/airports') +@ranking_blueprint.route('/ranking/airports') def airports(): data = _handle_request(Airport, 'takeoff_airport_id') diff --git a/skylines/frontend/views/search.py b/skylines/frontend/views/search.py index ed68cd4c05..0051695c05 100644 --- a/skylines/frontend/views/search.py +++ b/skylines/frontend/views/search.py @@ -11,7 +11,7 @@ MODELS = [User, Club, Airport] -@search_blueprint.route('/api/search', strict_slashes=False) +@search_blueprint.route('/search', strict_slashes=False) def index(): search_text = request.values.get('text', '').strip() if not search_text: diff --git a/skylines/frontend/views/settings.py b/skylines/frontend/views/settings.py index 4cc67803e1..69fee2b67d 100644 --- a/skylines/frontend/views/settings.py +++ b/skylines/frontend/views/settings.py @@ -38,13 +38,13 @@ def handle_user_param(): abort(403) -@settings_blueprint.route('/api/settings', strict_slashes=False) +@settings_blueprint.route('/settings', strict_slashes=False) def read(): schema = CurrentUserSchema(exclude=('id')) return jsonify(**schema.dump(g.user).data) -@settings_blueprint.route('/api/settings', methods=['POST'], strict_slashes=False) +@settings_blueprint.route('/settings', methods=['POST'], strict_slashes=False) def update(): json = request.get_json() if json is None: @@ -121,7 +121,7 @@ def update(): return jsonify() -@settings_blueprint.route('/api/settings/password/check', methods=['POST']) +@settings_blueprint.route('/settings/password/check', methods=['POST']) def check_current_password(): json = request.get_json() if not json: @@ -130,7 +130,7 @@ def check_current_password(): return jsonify(result=g.user.validate_password(json.get('password', ''))) -@settings_blueprint.route('/api/settings/tracking/key', methods=['POST']) +@settings_blueprint.route('/settings/tracking/key', methods=['POST']) def tracking_generate_key(): g.user.generate_tracking_key() db.session.commit() @@ -138,7 +138,7 @@ def tracking_generate_key(): return jsonify(key=g.user.tracking_key_hex) -@settings_blueprint.route('/api/settings/club', methods=['PUT']) +@settings_blueprint.route('/settings/club', methods=['PUT']) def create_club(): json = request.get_json() if json is None: diff --git a/skylines/frontend/views/statistics.py b/skylines/frontend/views/statistics.py index d05966e326..810e9c182a 100644 --- a/skylines/frontend/views/statistics.py +++ b/skylines/frontend/views/statistics.py @@ -8,8 +8,8 @@ statistics_blueprint = Blueprint('statistics', 'skylines') -@statistics_blueprint.route('/api/statistics', strict_slashes=False) -@statistics_blueprint.route('/api/statistics//') +@statistics_blueprint.route('/statistics', strict_slashes=False) +@statistics_blueprint.route('/statistics//') def index(page=None, id=None): name = None diff --git a/skylines/frontend/views/timeline.py b/skylines/frontend/views/timeline.py index dc56a553c2..54c4100086 100644 --- a/skylines/frontend/views/timeline.py +++ b/skylines/frontend/views/timeline.py @@ -9,7 +9,7 @@ timeline_blueprint = Blueprint('timeline', 'skylines') -@timeline_blueprint.route('/api/timeline') +@timeline_blueprint.route('/timeline') def list(): query = Event.query() \ .options(subqueryload('actor')) \ diff --git a/skylines/frontend/views/track.py b/skylines/frontend/views/track.py index fe07a52171..b827f22bc6 100644 --- a/skylines/frontend/views/track.py +++ b/skylines/frontend/views/track.py @@ -96,8 +96,8 @@ def _get_flight_path(pilot, threshold=0.001, last_update=None): # Use `live` alias here since `/api/tracking/*` is filtered by the "EasyPrivacy" adblocker list... -@track_blueprint.route('/api/tracking/', strict_slashes=False) -@track_blueprint.route('/api/live/', strict_slashes=False) +@track_blueprint.route('/tracking/', strict_slashes=False) +@track_blueprint.route('/live/', strict_slashes=False) def read(user_ids): pilots = get_requested_record_list(User, user_ids, joinedload=[User.club]) @@ -140,8 +140,8 @@ def read(user_ids): return jsonify(flights=flights, pilots=pilots_json) -@track_blueprint.route('/api/tracking//json') -@track_blueprint.route('/api/live//json') +@track_blueprint.route('/tracking//json') +@track_blueprint.route('/live//json') def json(user_id): pilot = get_requested_record(User, user_id, joinedload=[User.club]) last_update = request.values.get('last_update', 0, type=int) diff --git a/skylines/frontend/views/tracking.py b/skylines/frontend/views/tracking.py index 1f12bd6c6f..7a3f5f2e37 100644 --- a/skylines/frontend/views/tracking.py +++ b/skylines/frontend/views/tracking.py @@ -7,8 +7,8 @@ tracking_blueprint = Blueprint('tracking', 'skylines') -@tracking_blueprint.route('/api/tracking', strict_slashes=False) -@tracking_blueprint.route('/api/live', strict_slashes=False) +@tracking_blueprint.route('/tracking', strict_slashes=False) +@tracking_blueprint.route('/live', strict_slashes=False) def index(): fix_schema = TrackingFixSchema(only=('time', 'location', 'altitude', 'elevation', 'pilot')) airport_schema = AirportSchema(only=('id', 'name', 'countryCode')) @@ -41,7 +41,7 @@ def get_nearest_airport(track): return jsonify(friends=followers, tracks=tracks) -@tracking_blueprint.route('/api/tracking/latest.json') +@tracking_blueprint.route('/tracking/latest.json') @jsonp def latest(): fixes = [] diff --git a/skylines/frontend/views/upload.py b/skylines/frontend/views/upload.py index f05f2a0b2d..ec44e6a4cb 100644 --- a/skylines/frontend/views/upload.py +++ b/skylines/frontend/views/upload.py @@ -147,7 +147,7 @@ def _encode_flight_path(fp, qnh): igc_start_time=fp[0].datetime, igc_end_time=fp[-1].datetime) -@upload_blueprint.route('/api/flights/upload/csrf') +@upload_blueprint.route('/flights/upload/csrf') @login_required("You have to login to upload flights.") def csrf(): if not g.current_user: @@ -156,7 +156,7 @@ def csrf(): return jsonify(token=generate_csrf()) -@upload_blueprint.route('/api/flights/upload', methods=('POST',), strict_slashes=False) +@upload_blueprint.route('/flights/upload', methods=('POST',), strict_slashes=False) def index_post(): if not g.current_user: return jsonify(error='authentication-required'), 403 @@ -305,7 +305,7 @@ def index_post(): return jsonify(results=results, club_members=club_members, aircraft_models=aircraft_models) -@upload_blueprint.route('/api/flights/upload/verify', methods=('POST',)) +@upload_blueprint.route('/flights/upload/verify', methods=('POST',)) @login_required('You have to login to upload flights.') def verify(): json = request.get_json() @@ -374,7 +374,7 @@ def verify(): return jsonify() -@upload_blueprint.route('/api/flights/upload/airspace//.png') +@upload_blueprint.route('/flights/upload/airspace//.png') def airspace_image(cache_key, airspace_id): if not mapscript_available: abort(404) diff --git a/skylines/frontend/views/user.py b/skylines/frontend/views/user.py index 15edee9611..9a2077c6a3 100644 --- a/skylines/frontend/views/user.py +++ b/skylines/frontend/views/user.py @@ -88,7 +88,7 @@ def add_user_filter(query): db.session.commit() -@user_blueprint.route('/api/users/', strict_slashes=False) +@user_blueprint.route('/users/', strict_slashes=False) def read(user_id): user = get_requested_record(User, user_id) @@ -108,7 +108,7 @@ def read(user_id): return jsonify(**user_json) -@user_blueprint.route('/api/users//followers') +@user_blueprint.route('/users//followers') def followers(user_id): user = get_requested_record(User, user_id) @@ -127,7 +127,7 @@ def followers(user_id): return jsonify(followers=followers) -@user_blueprint.route('/api/users//following') +@user_blueprint.route('/users//following') def following(user_id): user = get_requested_record(User, user_id) @@ -165,7 +165,7 @@ def add_current_user_follows(followers): follower['currentUserFollows'] = (follower['id'] in current_user_follows) -@user_blueprint.route('/api/users//follow') +@user_blueprint.route('/users//follow') @login_required def follow(user_id): user = get_requested_record(User, user_id) @@ -175,7 +175,7 @@ def follow(user_id): return jsonify() -@user_blueprint.route('/api/users//unfollow') +@user_blueprint.route('/users//unfollow') @login_required def unfollow(user_id): user = get_requested_record(User, user_id) diff --git a/skylines/frontend/views/users.py b/skylines/frontend/views/users.py index 0f769925c7..a6671ce20c 100644 --- a/skylines/frontend/views/users.py +++ b/skylines/frontend/views/users.py @@ -16,7 +16,7 @@ users_blueprint = Blueprint('users', 'skylines') -@users_blueprint.route('/api/users', strict_slashes=False) +@users_blueprint.route('/users', strict_slashes=False) def list(): users = User.query() \ .options(joinedload(User.club)) \ @@ -32,7 +32,7 @@ def list(): return jsonify(users=UserSchema(only=fields).dump(users, many=True).data) -@users_blueprint.route('/api/users', methods=['POST'], strict_slashes=False) +@users_blueprint.route('/users', methods=['POST'], strict_slashes=False) def new_post(): json = request.get_json() if json is None: @@ -55,7 +55,7 @@ def new_post(): return jsonify(user=UserSchema().dump(user).data) -@users_blueprint.route('/api/users/recover', methods=['POST']) +@users_blueprint.route('/users/recover', methods=['POST']) def recover_post(): json = request.get_json() if json is None: @@ -136,7 +136,7 @@ def recover_step2_post(json): return jsonify() -@users_blueprint.route('/api/users/check-email', methods=['POST']) +@users_blueprint.route('/users/check-email', methods=['POST']) def check_email(): json = request.get_json() if not json: From 335e3cbe534cddea444f7f5e3f7a654bb00ef4f3 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 19:25:47 +0200 Subject: [PATCH 11/23] views: Group API blueprints --- skylines/frontend/views/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skylines/frontend/views/__init__.py b/skylines/frontend/views/__init__.py index 68466473e8..a5f522f2ac 100644 --- a/skylines/frontend/views/__init__.py +++ b/skylines/frontend/views/__init__.py @@ -32,16 +32,18 @@ def register(app): register_i18n(app) register_login(app) + app.register_blueprint(assets_blueprint) + app.register_blueprint(files_blueprint) + app.register_blueprint(lt24_blueprint) + app.register_blueprint(widgets_blueprint) + app.register_blueprint(about_blueprint, url_prefix='/api') app.register_blueprint(airport_blueprint, url_prefix='/api') app.register_blueprint(aircraft_models_blueprint, url_prefix='/api') - app.register_blueprint(assets_blueprint) app.register_blueprint(club_blueprint, url_prefix='/api') app.register_blueprint(clubs_blueprint, url_prefix='/api') - app.register_blueprint(files_blueprint) app.register_blueprint(flight_blueprint, url_prefix='/api') app.register_blueprint(flights_blueprint, url_prefix='/api') - app.register_blueprint(lt24_blueprint) app.register_blueprint(mapitems_blueprint, url_prefix='/api') app.register_blueprint(notifications_blueprint, url_prefix='/api') app.register_blueprint(ranking_blueprint, url_prefix='/api') @@ -54,4 +56,3 @@ def register(app): app.register_blueprint(upload_blueprint, url_prefix='/api') app.register_blueprint(user_blueprint, url_prefix='/api') app.register_blueprint(users_blueprint, url_prefix='/api') - app.register_blueprint(widgets_blueprint) From 807020f1933559e870710ceb7b3953e3aca7fe9a Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 19:51:28 +0200 Subject: [PATCH 12/23] views/login: Add route aliases with "/api" prefix --- skylines/frontend/views/login.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skylines/frontend/views/login.py b/skylines/frontend/views/login.py index 1f926ef79d..0971f5c8b9 100644 --- a/skylines/frontend/views/login.py +++ b/skylines/frontend/views/login.py @@ -36,6 +36,7 @@ def inject_current_user(): else: g.current_user = current_user + @app.route('/api/session', methods=('PUT',)) @app.route('/session', methods=('PUT',)) def create_session(): json = request.get_json() @@ -54,6 +55,7 @@ def create_session(): return jsonify() + @app.route('/api/session', methods=('DELETE',)) @app.route('/session', methods=('DELETE',)) def delete_session(): logout_user() From 1c420eeac5b2286968491401d2e07346a3a72fa6 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 19:59:43 +0200 Subject: [PATCH 13/23] authenticators/cookie: Use "/api/session" route for AJAX requests --- ember/app/authenticators/cookie.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ember/app/authenticators/cookie.js b/ember/app/authenticators/cookie.js index 574315fd4d..cb64ed0adc 100644 --- a/ember/app/authenticators/cookie.js +++ b/ember/app/authenticators/cookie.js @@ -5,7 +5,7 @@ export default Base.extend({ ajax: Ember.inject.service(), authenticate(email, password) { - return this.get('ajax').request('/session', { method: 'PUT', json: { email, password } }) + return this.get('ajax').request('/api/session', { method: 'PUT', json: { email, password } }) .then(() => this.get('ajax').request('/api/settings/')) .then(settings => ({ settings })); }, @@ -16,6 +16,6 @@ export default Base.extend({ }, invalidate(/* data */) { - return this.get('ajax').request('/session', { method: 'DELETE' }); + return this.get('ajax').request('/api/session', { method: 'DELETE' }); }, }); From 5aa521f7447312582dbbac3d4bdf976bd2467bc0 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:08:00 +0200 Subject: [PATCH 14/23] database: Move "Migrate" instance into "commands" app --- skylines/app.py | 3 +-- skylines/commands/__init__.py | 7 +++++-- skylines/database.py | 2 -- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/skylines/app.py b/skylines/app.py index 519bc1012f..4214f87dc3 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -23,9 +23,8 @@ def __init__(self, name='skylines', config_file=None, *args, **kw): def add_sqlalchemy(self): """ Create and configure SQLAlchemy extension """ - from skylines.database import db, migrate + from skylines.database import db db.init_app(self) - migrate.init_app(self, db) def add_cache(self): """ Create and attach Cache extension """ diff --git a/skylines/commands/__init__.py b/skylines/commands/__init__.py index 3e5f787834..800e6e852a 100644 --- a/skylines/commands/__init__.py +++ b/skylines/commands/__init__.py @@ -1,7 +1,7 @@ import sys from flask.ext.script import Manager -from flask.ext.migrate import MigrateCommand +from flask.ext.migrate import Migrate, MigrateCommand from .shell import Shell from .server import Server, APIServer @@ -19,6 +19,7 @@ from .search import Search from skylines.app import create_app +from skylines.database import db from config import to_envvar @@ -27,7 +28,9 @@ def _create_app(config): print 'Config file "{}" not found.'.format(config) sys.exit(1) - return create_app() + app = create_app() + app.migrate = Migrate(app, db) + return app manager = Manager(_create_app) diff --git a/skylines/database.py b/skylines/database.py index d91e8059c2..d132ae90ca 100644 --- a/skylines/database.py +++ b/skylines/database.py @@ -1,5 +1,4 @@ from flask.ext.sqlalchemy import SQLAlchemy -from flask.ext.migrate import Migrate def query(cls, **kw): @@ -20,7 +19,6 @@ def exists(cls, **kw): db = SQLAlchemy(session_options=dict(expire_on_commit=False)) -migrate = Migrate() db.Model.flask_query = db.Model.query db.Model.query = classmethod(query) From f3c283f880a4238e81e9f158291a15d7157d2a48 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:15:14 +0200 Subject: [PATCH 15/23] app: Move "Cache" instance into its own file --- skylines/app.py | 4 ++-- skylines/cache.py | 3 +++ skylines/frontend/views/tracking.py | 5 +++-- skylines/frontend/views/upload.py | 9 +++++---- skylines/model/flight.py | 6 +++--- 5 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 skylines/cache.py diff --git a/skylines/app.py b/skylines/app.py index 4214f87dc3..b5d081d1ab 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -28,8 +28,8 @@ def add_sqlalchemy(self): def add_cache(self): """ Create and attach Cache extension """ - from flask.ext.cache import Cache - self.cache = Cache(self, with_jinja2_ext=False) + from skylines.cache import cache + cache.init_app(self) def add_login_manager(self): """ Create and attach Login extension """ diff --git a/skylines/cache.py b/skylines/cache.py new file mode 100644 index 0000000000..0ea3d61436 --- /dev/null +++ b/skylines/cache.py @@ -0,0 +1,3 @@ +from flask.ext.cache import Cache + +cache = Cache() diff --git a/skylines/frontend/views/tracking.py b/skylines/frontend/views/tracking.py index 7a3f5f2e37..b574151c94 100644 --- a/skylines/frontend/views/tracking.py +++ b/skylines/frontend/views/tracking.py @@ -1,5 +1,6 @@ -from flask import Blueprint, current_app, jsonify, g +from flask import Blueprint, jsonify, g +from skylines.cache import cache from skylines.lib.decorators import jsonp from skylines.model import TrackingFix, Airport, Follower from skylines.schemas import TrackingFixSchema, AirportSchema @@ -13,7 +14,7 @@ def index(): fix_schema = TrackingFixSchema(only=('time', 'location', 'altitude', 'elevation', 'pilot')) airport_schema = AirportSchema(only=('id', 'name', 'countryCode')) - @current_app.cache.memoize(timeout=(60 * 60)) + @cache.memoize(timeout=(60 * 60)) def get_nearest_airport(track): airport = Airport.by_location(track.location, None) if not airport: diff --git a/skylines/frontend/views/upload.py b/skylines/frontend/views/upload.py index ec44e6a4cb..0d7275b293 100644 --- a/skylines/frontend/views/upload.py +++ b/skylines/frontend/views/upload.py @@ -12,6 +12,7 @@ from redis.exceptions import ConnectionError from sqlalchemy.sql.expression import func +from skylines.cache import cache from skylines.database import db from skylines.lib import files from skylines.lib.util import pressure_alt_to_qnh_alt @@ -270,8 +271,8 @@ def index_post(): # Store data in cache for image creation cache_key = hashlib.sha1(str(flight.id) + '_' + str(user.id)).hexdigest() - current_app.cache.set('upload_airspace_infringements_' + cache_key, infringements, timeout=15 * 60) - current_app.cache.set('upload_airspace_flight_path_' + cache_key, fp, timeout=15 * 60) + cache.set('upload_airspace_infringements_' + cache_key, infringements, timeout=15 * 60) + cache.set('upload_airspace_flight_path_' + cache_key, fp, timeout=15 * 60) airspace = db.session.query(Airspace) \ .filter(Airspace.id.in_(infringements.keys())) \ @@ -380,8 +381,8 @@ def airspace_image(cache_key, airspace_id): abort(404) # get information from cache... - infringements = current_app.cache.get('upload_airspace_infringements_' + cache_key) - flight_path = current_app.cache.get('upload_airspace_flight_path_' + cache_key) + infringements = cache.get('upload_airspace_infringements_' + cache_key) + flight_path = cache.get('upload_airspace_flight_path_' + cache_key) # abort if invalid cache key if not infringements \ diff --git a/skylines/model/flight.py b/skylines/model/flight.py index 411826e687..0c464a1c8c 100644 --- a/skylines/model/flight.py +++ b/skylines/model/flight.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta from bisect import bisect_left -from flask import current_app from sqlalchemy.dialects import postgresql from sqlalchemy.orm import deferred @@ -15,6 +14,7 @@ from geoalchemy2.shape import to_shape, from_shape from shapely.geometry import LineString +from skylines.cache import cache from skylines.database import db from skylines.lib.sql import _ST_Intersects, _ST_Contains @@ -491,7 +491,7 @@ def update_flight_path(flight): def get_elevations_for_flight(flight): - cached_elevations = current_app.cache.get('elevations_' + flight.__repr__()) + cached_elevations = cache.get('elevations_' + flight.__repr__()) if cached_elevations: return cached_elevations @@ -544,6 +544,6 @@ def get_elevations_for_flight(flight): elevations.append((time, elevation)) - current_app.cache.set('elevations_' + flight.__repr__(), elevations, timeout=3600 * 24) + cache.set('elevations_' + flight.__repr__(), elevations, timeout=3600 * 24) return elevations From f6707832286a31d0c8aea818325f0d7c042192d7 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:18:18 +0200 Subject: [PATCH 16/23] app: Move "LoginManager" instance into its own file --- skylines/app.py | 5 ++--- skylines/frontend/login.py | 23 +++++++++++++++++++++++ skylines/frontend/views/login.py | 16 ---------------- 3 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 skylines/frontend/login.py diff --git a/skylines/app.py b/skylines/app.py index b5d081d1ab..ed0ee47892 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -33,9 +33,8 @@ def add_cache(self): def add_login_manager(self): """ Create and attach Login extension """ - from flask.ext.login import LoginManager - self.login_manager = LoginManager() - self.login_manager.init_app(self) + from skylines.frontend.login import login_manager + login_manager.init_app(self) def add_logging_handlers(self): if self.debug: return diff --git a/skylines/frontend/login.py b/skylines/frontend/login.py new file mode 100644 index 0000000000..8c39f5da43 --- /dev/null +++ b/skylines/frontend/login.py @@ -0,0 +1,23 @@ +import base64 + +from flask.ext.login import LoginManager + +from skylines.model import User + +login_manager = LoginManager() + + +@login_manager.user_loader +def load_user(user_id): + return User.get(user_id) + + +@login_manager.header_loader +def load_user_from_header(header_val): + try: + header_val = header_val.replace('Basic ', '', 1) + header_val = base64.b64decode(header_val) + email, password = header_val.split(':', 1) + return User.by_credentials(email, password) + except: + return None diff --git a/skylines/frontend/views/login.py b/skylines/frontend/views/login.py index 0971f5c8b9..3a306edbba 100644 --- a/skylines/frontend/views/login.py +++ b/skylines/frontend/views/login.py @@ -1,5 +1,3 @@ -import base64 - from flask import request, g, jsonify from flask.ext.login import login_user, logout_user, current_user @@ -10,20 +8,6 @@ def register(app): """ Register the /login and /logout routes on the given app """ - @app.login_manager.user_loader - def load_user(userid): - return User.get(userid) - - @app.login_manager.header_loader - def load_user_from_header(header_val): - try: - header_val = header_val.replace('Basic ', '', 1) - header_val = base64.b64decode(header_val) - email, password = header_val.split(':', 1) - return User.by_credentials(email, password) - except: - return None - @app.before_request def inject_current_user(): """ From 34d94277e946156e4cb10179380647921faa13ee Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:18:25 +0200 Subject: [PATCH 17/23] app: Move "Sentry" instance into its own file --- skylines/app.py | 8 +++----- skylines/sentry.py | 3 +++ skylines/tracking/server.py | 12 +++--------- 3 files changed, 9 insertions(+), 14 deletions(-) create mode 100644 skylines/sentry.py diff --git a/skylines/app.py b/skylines/app.py index ed0ee47892..820cf2e57c 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -2,7 +2,6 @@ import config from flask import Flask -from raven.contrib.flask import Sentry from skylines.api.middleware import HTTPMethodOverrideMiddleware @@ -59,9 +58,8 @@ def add_logging_handlers(self): self.logger.addHandler(file_handler) def add_sentry(self): - sentry_dsn = self.config.get('SENTRY_DSN') - if sentry_dsn: - Sentry(self, dsn=sentry_dsn) + from skylines.sentry import sentry + sentry.init_app(self) def add_celery(self): from skylines.worker.celery import celery @@ -77,6 +75,7 @@ def create_app(*args, **kw): app = SkyLines(*args, **kw) app.add_sqlalchemy() app.add_cache() + app.add_sentry() app.initialize_lib() return app @@ -85,7 +84,6 @@ def create_http_app(*args, **kw): app = create_app(*args, **kw) app.add_logging_handlers() - app.add_sentry() app.add_celery() return app diff --git a/skylines/sentry.py b/skylines/sentry.py new file mode 100644 index 0000000000..20ddeef3cd --- /dev/null +++ b/skylines/sentry.py @@ -0,0 +1,3 @@ +from raven.contrib.flask import Sentry + +sentry = Sentry() diff --git a/skylines/tracking/server.py b/skylines/tracking/server.py index 86d7b5513f..bdd7d5aa5f 100644 --- a/skylines/tracking/server.py +++ b/skylines/tracking/server.py @@ -3,13 +3,13 @@ from datetime import datetime, time, timedelta from gevent.server import DatagramServer -from raven import Client as RavenClient from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.sql.expression import or_ from skylines.database import db from skylines.model import User, TrackingFix, Follower, Elevation +from skylines.sentry import sentry from skylines.tracking.crc import check_crc, set_crc # More information about this protocol can be found in the XCSoar @@ -280,17 +280,11 @@ def __handle(self, data, (host, port)): def handle(self, data, address): try: self.__handle(data, address) - except Exception as e: - if self.raven: - self.raven.captureException() - else: - print e + except Exception: + sentry.captureException() def serve_forever(self, **kwargs): if not self.app: raise RuntimeError('application not registered on server instance') - sentry_dsn = self.app.config.get('SENTRY_DSN') - self.raven = RavenClient(dsn=sentry_dsn) if sentry_dsn else None - super(TrackingServer, self).serve_forever(**kwargs) From b39d15423bd789c0e8ca30729b4a52fa3d8adaa7 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:24:04 +0200 Subject: [PATCH 18/23] app: Remove unused return statement from add_celery() method --- skylines/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skylines/app.py b/skylines/app.py index 820cf2e57c..2f66a3dd47 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -64,7 +64,6 @@ def add_sentry(self): def add_celery(self): from skylines.worker.celery import celery celery.init_app(self) - return celery def initialize_lib(self): from skylines.lib.geoid import load_geoid From bd9c4d09ec3b7a7671b5f2a0344841ac1219e719 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:25:19 +0200 Subject: [PATCH 19/23] app: Move load_egm96() call into create_frontend_app() That code is not needed by the other apps --- skylines/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skylines/app.py b/skylines/app.py index 2f66a3dd47..56f9720a2e 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -65,7 +65,7 @@ def add_celery(self): from skylines.worker.celery import celery celery.init_app(self) - def initialize_lib(self): + def load_egm96(self): from skylines.lib.geoid import load_geoid load_geoid(self) @@ -75,7 +75,6 @@ def create_app(*args, **kw): app.add_sqlalchemy() app.add_cache() app.add_sentry() - app.initialize_lib() return app @@ -91,6 +90,7 @@ def create_http_app(*args, **kw): def create_frontend_app(*args, **kw): app = create_http_app('skylines.frontend', *args, **kw) + app.load_egm96() app.add_login_manager() import skylines.frontend.views From 067cc7d5848176995dce4ed93a7c3d70655c68a3 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 20:52:50 +0200 Subject: [PATCH 20/23] model/flight: Move get_elevations_for_flight() into "views/flight" module --- skylines/frontend/views/flight.py | 64 ++++++++++++++++++++++++++++++- skylines/model/flight.py | 61 ----------------------------- 2 files changed, 62 insertions(+), 63 deletions(-) diff --git a/skylines/frontend/views/flight.py b/skylines/frontend/views/flight.py index 5bae25d6c4..86d40575a7 100644 --- a/skylines/frontend/views/flight.py +++ b/skylines/frontend/views/flight.py @@ -3,11 +3,13 @@ from flask import Blueprint, request, abort, current_app, jsonify, g, make_response +from sqlalchemy import literal_column, and_ from sqlalchemy.orm import undefer_group, contains_eager from sqlalchemy.sql.expression import func from geoalchemy2.shape import to_shape from datetime import timedelta +from skylines.cache import cache from skylines.database import db from skylines.lib import files from skylines.lib.dbutil import get_requested_record @@ -16,11 +18,10 @@ from skylines.lib.geo import METERS_PER_DEGREE from skylines.lib.geoid import egm96_height from skylines.model import ( - User, Flight, Location, FlightComment, + User, Flight, Elevation, Location, FlightComment, Notification, Event, FlightMeetings, AircraftModel, ) from skylines.model.event import create_flight_comment_notifications -from skylines.model.flight import get_elevations_for_flight from skylines.schemas import fields, FlightSchema, FlightCommentSchema, FlightPhaseSchema, ContestLegSchema, Schema, ValidationError from skylines.worker import tasks from redis.exceptions import ConnectionError @@ -497,3 +498,62 @@ def add_comment(flight_id): db.session.commit() return jsonify() + + +def get_elevations_for_flight(flight): + cached_elevations = cache.get('elevations_' + flight.__repr__()) + if cached_elevations: + return cached_elevations + + ''' + WITH src AS + (SELECT ST_DumpPoints(flights.locations) AS location, + flights.timestamps AS timestamps, + flights.locations AS locations + FROM flights + WHERE flights.id = 30000) + SELECT timestamps[(src.location).path[1]] AS timestamp, + ST_Value(elevations.rast, (src.location).geom) AS elevation + FROM elevations, src + WHERE src.locations && elevations.rast AND (src.location).geom && elevations.rast; + ''' + + # Prepare column expressions + location = Flight.locations.ST_DumpPoints() + + # Prepare cte + cte = db.session.query(location.label('location'), + Flight.locations.label('locations'), + Flight.timestamps.label('timestamps')) \ + .filter(Flight.id == flight.id).cte() + + # Prepare column expressions + timestamp = literal_column('timestamps[(location).path[1]]') + elevation = Elevation.rast.ST_Value(cte.c.location.geom) + + # Prepare main query + q = db.session.query(timestamp.label('timestamp'), + elevation.label('elevation')) \ + .filter(and_(cte.c.locations.intersects(Elevation.rast), + cte.c.location.geom.intersects(Elevation.rast))).all() + + if len(q) == 0: + return [] + + start_time = q[0][0] + start_midnight = start_time.replace(hour=0, minute=0, second=0, + microsecond=0) + + elevations = [] + for time, elevation in q: + if elevation is None: + continue + + time_delta = time - start_midnight + time = time_delta.days * 86400 + time_delta.seconds + + elevations.append((time, elevation)) + + cache.set('elevations_' + flight.__repr__(), elevations, timeout=3600 * 24) + + return elevations diff --git a/skylines/model/flight.py b/skylines/model/flight.py index 0c464a1c8c..d23e110f69 100644 --- a/skylines/model/flight.py +++ b/skylines/model/flight.py @@ -14,14 +14,12 @@ from geoalchemy2.shape import to_shape, from_shape from shapely.geometry import LineString -from skylines.cache import cache from skylines.database import db from skylines.lib.sql import _ST_Intersects, _ST_Contains from .geo import Location from .igcfile import IGCFile from .aircraft_model import AircraftModel -from .elevation import Elevation from .contest_leg import ContestLeg @@ -488,62 +486,3 @@ def update_flight_path(flight): db.session.commit() return True - - -def get_elevations_for_flight(flight): - cached_elevations = cache.get('elevations_' + flight.__repr__()) - if cached_elevations: - return cached_elevations - - ''' - WITH src AS - (SELECT ST_DumpPoints(flights.locations) AS location, - flights.timestamps AS timestamps, - flights.locations AS locations - FROM flights - WHERE flights.id = 30000) - SELECT timestamps[(src.location).path[1]] AS timestamp, - ST_Value(elevations.rast, (src.location).geom) AS elevation - FROM elevations, src - WHERE src.locations && elevations.rast AND (src.location).geom && elevations.rast; - ''' - - # Prepare column expressions - location = Flight.locations.ST_DumpPoints() - - # Prepare cte - cte = db.session.query(location.label('location'), - Flight.locations.label('locations'), - Flight.timestamps.label('timestamps')) \ - .filter(Flight.id == flight.id).cte() - - # Prepare column expressions - timestamp = literal_column('timestamps[(location).path[1]]') - elevation = Elevation.rast.ST_Value(cte.c.location.geom) - - # Prepare main query - q = db.session.query(timestamp.label('timestamp'), - elevation.label('elevation')) \ - .filter(and_(cte.c.locations.intersects(Elevation.rast), - cte.c.location.geom.intersects(Elevation.rast))).all() - - if len(q) == 0: - return [] - - start_time = q[0][0] - start_midnight = start_time.replace(hour=0, minute=0, second=0, - microsecond=0) - - elevations = [] - for time, elevation in q: - if elevation is None: - continue - - time_delta = time - start_midnight - time = time_delta.days * 86400 + time_delta.seconds - - elevations.append((time, elevation)) - - cache.set('elevations_' + flight.__repr__(), elevations, timeout=3600 * 24) - - return elevations From babda32875218214b76809bbaeec97e39e990aa9 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 21:01:59 +0200 Subject: [PATCH 21/23] app: Move "cache" into "frontend" --- skylines/app.py | 4 ++-- skylines/{ => frontend}/cache.py | 0 skylines/frontend/views/flight.py | 2 +- skylines/frontend/views/tracking.py | 2 +- skylines/frontend/views/upload.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename skylines/{ => frontend}/cache.py (100%) diff --git a/skylines/app.py b/skylines/app.py index 56f9720a2e..69add401e5 100644 --- a/skylines/app.py +++ b/skylines/app.py @@ -27,7 +27,7 @@ def add_sqlalchemy(self): def add_cache(self): """ Create and attach Cache extension """ - from skylines.cache import cache + from skylines.frontend.cache import cache cache.init_app(self) def add_login_manager(self): @@ -73,7 +73,6 @@ def load_egm96(self): def create_app(*args, **kw): app = SkyLines(*args, **kw) app.add_sqlalchemy() - app.add_cache() app.add_sentry() return app @@ -90,6 +89,7 @@ def create_http_app(*args, **kw): def create_frontend_app(*args, **kw): app = create_http_app('skylines.frontend', *args, **kw) + app.add_cache() app.load_egm96() app.add_login_manager() diff --git a/skylines/cache.py b/skylines/frontend/cache.py similarity index 100% rename from skylines/cache.py rename to skylines/frontend/cache.py diff --git a/skylines/frontend/views/flight.py b/skylines/frontend/views/flight.py index 86d40575a7..524952af63 100644 --- a/skylines/frontend/views/flight.py +++ b/skylines/frontend/views/flight.py @@ -9,7 +9,7 @@ from geoalchemy2.shape import to_shape from datetime import timedelta -from skylines.cache import cache +from skylines.frontend.cache import cache from skylines.database import db from skylines.lib import files from skylines.lib.dbutil import get_requested_record diff --git a/skylines/frontend/views/tracking.py b/skylines/frontend/views/tracking.py index b574151c94..a1774eeee5 100644 --- a/skylines/frontend/views/tracking.py +++ b/skylines/frontend/views/tracking.py @@ -1,6 +1,6 @@ from flask import Blueprint, jsonify, g -from skylines.cache import cache +from skylines.frontend.cache import cache from skylines.lib.decorators import jsonp from skylines.model import TrackingFix, Airport, Follower from skylines.schemas import TrackingFixSchema, AirportSchema diff --git a/skylines/frontend/views/upload.py b/skylines/frontend/views/upload.py index 0d7275b293..1e672c6101 100644 --- a/skylines/frontend/views/upload.py +++ b/skylines/frontend/views/upload.py @@ -12,7 +12,7 @@ from redis.exceptions import ConnectionError from sqlalchemy.sql.expression import func -from skylines.cache import cache +from skylines.frontend.cache import cache from skylines.database import db from skylines.lib import files from skylines.lib.util import pressure_alt_to_qnh_alt From 81be8212acba0b57557b5576515587918bb8d74e Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 21:18:06 +0200 Subject: [PATCH 22/23] views/settings: Remove "before_request" handler --- skylines/frontend/views/settings.py | 83 +++++++++++------------------ 1 file changed, 31 insertions(+), 52 deletions(-) diff --git a/skylines/frontend/views/settings.py b/skylines/frontend/views/settings.py index 69fee2b67d..ad75d7a97e 100644 --- a/skylines/frontend/views/settings.py +++ b/skylines/frontend/views/settings.py @@ -1,9 +1,8 @@ -from flask import Blueprint, request, abort, g, jsonify - +from flask import Blueprint, request, g, jsonify +from flask.ext.login import login_required from sqlalchemy.sql.expression import and_, or_ from skylines.database import db -from skylines.lib.dbutil import get_requested_record from skylines.model import User, Club, Flight, IGCFile from skylines.model.event import ( create_club_join_event @@ -13,38 +12,15 @@ settings_blueprint = Blueprint('settings', 'skylines') -@settings_blueprint.before_request -def handle_user_param(): - """ - Extracts the `user` parameter from request.values, queries the - corresponding User model and checks if the model is writeable by the - current user. - """ - - if request.endpoint == 'settings.html': - return - - if not g.current_user: - abort(401) - - g.user_id = request.values.get('user') - - if g.user_id: - g.user = get_requested_record(User, g.user_id) - else: - g.user = g.current_user - - if not g.user.is_writable(g.current_user): - abort(403) - - @settings_blueprint.route('/settings', strict_slashes=False) +@login_required def read(): schema = CurrentUserSchema(exclude=('id')) - return jsonify(**schema.dump(g.user).data) + return jsonify(**schema.dump(g.current_user).data) @settings_blueprint.route('/settings', methods=['POST'], strict_slashes=False) +@login_required def update(): json = request.get_json() if json is None: @@ -58,61 +34,61 @@ def update(): if 'email_address' in data: email = data.get('email_address') - if email != g.user.email_address and User.exists(email_address=email): + if email != g.current_user.email_address and User.exists(email_address=email): return jsonify(error='email-exists-already'), 422 - g.user.email_address = email + g.current_user.email_address = email if 'first_name' in data: - g.user.first_name = data.get('first_name') + g.current_user.first_name = data.get('first_name') if 'last_name' in data: - g.user.last_name = data.get('last_name') + g.current_user.last_name = data.get('last_name') if 'distance_unit' in data: - g.user.distance_unit = data.get('distance_unit') + g.current_user.distance_unit = data.get('distance_unit') if 'speed_unit' in data: - g.user.speed_unit = data.get('speed_unit') + g.current_user.speed_unit = data.get('speed_unit') if 'lift_unit' in data: - g.user.lift_unit = data.get('lift_unit') + g.current_user.lift_unit = data.get('lift_unit') if 'altitude_unit' in data: - g.user.altitude_unit = data.get('altitude_unit') + g.current_user.altitude_unit = data.get('altitude_unit') if 'tracking_callsign' in data: - g.user.tracking_callsign = data.get('tracking_callsign') + g.current_user.tracking_callsign = data.get('tracking_callsign') if 'tracking_delay' in data: - g.user.tracking_delay = data.get('tracking_delay') + g.current_user.tracking_delay = data.get('tracking_delay') if 'password' in data: if 'currentPassword' not in data: return jsonify(error='current-password-missing'), 422 - if not g.user.validate_password(data['currentPassword']): + if not g.current_user.validate_password(data['currentPassword']): return jsonify(error='wrong-password'), 403 - g.user.password = data['password'] - g.user.recover_key = None + g.current_user.password = data['password'] + g.current_user.recover_key = None - if 'club_id' in data and data['club_id'] != g.user.club_id: + if 'club_id' in data and data['club_id'] != g.current_user.club_id: club_id = data['club_id'] if club_id is not None and not Club.exists(id=club_id): return jsonify(error='unknown-club'), 422 - g.user.club_id = club_id + g.current_user.club_id = club_id - create_club_join_event(club_id, g.user) + create_club_join_event(club_id, g.current_user) # assign the user's new club to all of his flights that have # no club yet flights = Flight.query().join(IGCFile) flights = flights.filter(and_(Flight.club_id == None, - or_(Flight.pilot_id == g.user.id, - IGCFile.owner_id == g.user.id))) + or_(Flight.pilot_id == g.current_user.id, + IGCFile.owner_id == g.current_user.id))) for flight in flights: flight.club_id = club_id @@ -122,23 +98,26 @@ def update(): @settings_blueprint.route('/settings/password/check', methods=['POST']) +@login_required def check_current_password(): json = request.get_json() if not json: return jsonify(error='invalid-request'), 400 - return jsonify(result=g.user.validate_password(json.get('password', ''))) + return jsonify(result=g.current_user.validate_password(json.get('password', ''))) @settings_blueprint.route('/settings/tracking/key', methods=['POST']) +@login_required def tracking_generate_key(): - g.user.generate_tracking_key() + g.current_user.generate_tracking_key() db.session.commit() - return jsonify(key=g.user.tracking_key_hex) + return jsonify(key=g.current_user.tracking_key_hex) @settings_blueprint.route('/settings/club', methods=['PUT']) +@login_required def create_club(): json = request.get_json() if json is None: @@ -159,10 +138,10 @@ def create_club(): db.session.flush() # assign the user to the new club - g.user.club = club + g.current_user.club = club # create the "user joined club" event - create_club_join_event(club.id, g.user) + create_club_join_event(club.id, g.current_user) db.session.commit() From 8925de30b2c75dd5a105be2a9d953863825a3a91 Mon Sep 17 00:00:00 2001 From: Tobias Bieniek Date: Tue, 25 Oct 2016 21:43:41 +0200 Subject: [PATCH 23/23] Bower: Remove obsolete "jQuery-ajaxTransport-XDomainRequest" dependency --- ember/bower.json | 1 - ember/ember-cli-build.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/ember/bower.json b/ember/bower.json index 302812dc5d..4b030a44cb 100644 --- a/ember/bower.json +++ b/ember/bower.json @@ -13,7 +13,6 @@ "Flot": "flot#0.8.3", "flot-marks": "https://github.com/TobiasLohner/flot-marks.git#f09ded70f5a229a38ba0b9cfa92dbb448ca4daaf", "jquery": "1.10.2", - "jQuery-ajaxTransport-XDomainRequest": "jquery-ajaxtransport-xdomainrequest#1.0.3", "sidebar-v2": "0.2.1", "eonasdan-bootstrap-datetimepicker": "https://github.com/TobiasLohner/bootstrap-datetimepicker.git#c36342415a1be8fa013548402bf01718ca93d454", "BigScreen": "bigscreen#2.0.4" diff --git a/ember/ember-cli-build.js b/ember/ember-cli-build.js index feadc07f3e..91dc662fe0 100644 --- a/ember/ember-cli-build.js +++ b/ember/ember-cli-build.js @@ -31,8 +31,6 @@ module.exports = function(defaults) { // please specify an object with the list of modules as keys // along with the exports of each module as its value. - app.import('bower_components/jQuery-ajaxTransport-XDomainRequest/jquery.xdomainrequest.min.js'); - app.import('vendor/openlayers/ol3cesium.js'); app.import('vendor/openlayers/ol.css');