Beispiel #1
0
    def run(self, force, date_from, date_to, ids):
        current_app.add_celery()

        if force:
            # invalidate all results
            Flight.query().update({'needs_analysis': True})

        if ids:
            for flight_id in ids:
                self.do(flight_id)
        elif date_from and date_to:
            print date_from
            try:
                date_from = datetime.strptime(date_from, "%Y-%m-%d")
                date_to = datetime.strptime(date_to, "%Y-%m-%d")
            except:
                print "Cannot parse date."
                quit()

            q = db.session.query(Flight)
            q = q.filter(Flight.takeoff_time >= date_from) \
                 .filter(Flight.takeoff_time <= date_to)

            for flight in q:
                self.do(flight.id)
        else:
            for flight in Flight.query(needs_analysis=True):
                self.do(flight.id)
Beispiel #2
0
    def run(self, force, date_from, date_to, ids):
        current_app.add_celery()

        if force:
            # invalidate all results
            Flight.query().update({'needs_analysis': True})

        if ids:
            for flight_id in ids:
                self.do(flight_id)
        elif date_from and date_to:
            print date_from
            try:
                date_from = datetime.strptime(date_from, "%Y-%m-%d")
                date_to = datetime.strptime(date_to, "%Y-%m-%d")
            except:
                print "Cannot parse date."
                quit()

            q = db.session.query(Flight)
            q = q.filter(Flight.takeoff_time >= date_from) \
                 .filter(Flight.takeoff_time <= date_to)

            for flight in q:
                self.do(flight.id)
        else:
            for flight in Flight.query(needs_analysis=True):
                self.do(flight.id)
Beispiel #3
0
    def run(self, force, ids):
        current_app.add_celery()

        if force:
            # invalidate all results
            Flight.query().update({'needs_analysis': True})

        if ids:
            for flight_id in ids:
                self.do(flight_id)
        else:
            for flight in Flight.query(needs_analysis=True):
                self.do(flight.id)
Beispiel #4
0
    def run(self, force, ids):
        current_app.add_celery()

        if force:
            # invalidate all results
            Flight.query().update({'needs_analysis': True})

        if ids:
            for flight_id in ids:
                self.do(flight_id)
        else:
            for flight in Flight.query(needs_analysis=True):
                self.do(flight.id)
Beispiel #5
0
def _get_distance_flight(distance):
    return Flight.query() \
        .filter(Flight.pilot == g.user) \
        .filter(Flight.olc_classic_distance >= distance) \
        .order_by(Flight.landing_time) \
        .filter(Flight.is_rankable()) \
        .first()
Beispiel #6
0
def club_change_post(form):
    old_club_id = g.user.club_id
    new_club_id = form.club.data if form.club.data != 0 else None

    if old_club_id == new_club_id:
        return redirect(url_for('.club', user=g.user_id))

    g.user.club_id = new_club_id

    create_club_join_event(new_club_id, g.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)))
    for flight in flights:
        flight.club_id = g.user.club_id

    db.session.commit()

    flash(_('New club was saved.'), 'success')

    return redirect(url_for('.club', user=g.user_id))
Beispiel #7
0
def _get_distance_flight(distance):
    return Flight.query() \
        .filter(Flight.pilot == g.user) \
        .filter(Flight.olc_classic_distance >= distance) \
        .order_by(Flight.landing_time) \
        .filter(Flight.is_rankable()) \
        .first()
Beispiel #8
0
def club_change_post(form):
    old_club_id = g.user.club_id
    new_club_id = form.club.data if form.club.data != 0 else None

    if old_club_id == new_club_id:
        return redirect(url_for('.club', user=g.user_id))

    g.user.club_id = new_club_id

    create_club_join_event(new_club_id, g.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)))
    for flight in flights:
        flight.club_id = g.user.club_id

    db.session.commit()

    flash(_('New club was saved.'), 'success')

    return redirect(url_for('.club', user=g.user_id))
Beispiel #9
0
def _distance_flight(user, distance, schema):
    flight = (Flight.query().filter(Flight.pilot == user).filter(
        Flight.olc_classic_distance >= distance).order_by(
            Flight.landing_time).filter(Flight.is_rankable()).first())

    if flight:
        return schema.dump(flight).data
Beispiel #10
0
def _get_near_flights(flight, location, time, max_distance=1000):
    # calculate max_distance in degrees at the earth's sphere (approximate,
    # cutoff at +-85 deg)
    max_distance_deg = (max_distance / METERS_PER_DEGREE) / \
        math.cos(math.radians(min(abs(location.latitude), 85)))

    # the distance filter is geometric only, so max_distance must be given in
    # SRID units (which is degrees for WGS84). The filter will be more and more
    # inaccurate further to the poles. But it's a lot faster than the geograpic
    # filter...

    result = Flight.query() \
        .options(undefer_group('path')) \
        .filter(Flight.id != flight.id) \
        .filter(Flight.takeoff_time <= time) \
        .filter(Flight.landing_time >= time) \
        .filter(func.ST_DWithin(Flight.locations,
                                location.to_wkt_element(),
                                max_distance_deg))

    result = _patch_query(result)

    flights = []
    for flight in result:
        # find point closest to given time
        closest = min(range(len(flight.timestamps)),
                      key=lambda x: abs((flight.timestamps[x] - time).total_seconds()))

        trace = to_shape(flight.locations).coords

        if closest == 0 or closest == len(trace) - 1:
            point = trace[closest]
        else:
            # interpolate flight trace between two fixes
            next_smaller = closest if flight.timestamps[closest] < time else closest - 1
            next_larger = closest if flight.timestamps[closest] > time else closest + 1
            dx = (time - flight.timestamps[next_smaller]).total_seconds() / \
                 (flight.timestamps[next_larger] - flight.timestamps[next_smaller]).total_seconds()

            point_next = trace[closest]
            point_prev = trace[closest]

            point = [point_prev[0] + (point_next[0] - point_prev[0]) * dx,
                     point_prev[1] + (point_next[1] - point_prev[1]) * dx]

        point_distance = location.geographic_distance(
            Location(latitude=point[1], longitude=point[0]))

        if point_distance > max_distance:
            continue

        flights.append(flight)

        # limit to 5 flights
        if len(flights) == 5:
            break

    return flights
Beispiel #11
0
def _get_near_flights(flight, location, time, max_distance=1000):
    # calculate max_distance in degrees at the earth's sphere (approximate,
    # cutoff at +-85 deg)
    max_distance_deg = (max_distance / METERS_PER_DEGREE) / \
        math.cos(math.radians(min(abs(location.latitude), 85)))

    # the distance filter is geometric only, so max_distance must be given in
    # SRID units (which is degrees for WGS84). The filter will be more and more
    # inaccurate further to the poles. But it's a lot faster than the geograpic
    # filter...

    result = Flight.query() \
        .options(undefer_group('path')) \
        .filter(Flight.id != flight.id) \
        .filter(Flight.takeoff_time <= time) \
        .filter(Flight.landing_time >= time) \
        .filter(func.ST_DWithin(Flight.locations,
                                location.to_wkt_element(),
                                max_distance_deg))

    result = _patch_query(result)

    flights = []
    for flight in result:
        # find point closest to given time
        closest = min(range(len(flight.timestamps)),
                      key=lambda x: abs((flight.timestamps[x] - time).total_seconds()))

        trace = to_shape(flight.locations).coords

        if closest == 0 or closest == len(trace) - 1:
            point = trace[closest]
        else:
            # interpolate flight trace between two fixes
            next_smaller = closest if flight.timestamps[closest] < time else closest - 1
            next_larger = closest if flight.timestamps[closest] > time else closest + 1
            dx = (time - flight.timestamps[next_smaller]).total_seconds() / \
                 (flight.timestamps[next_larger] - flight.timestamps[next_smaller]).total_seconds()

            point_next = trace[closest]
            point_prev = trace[closest]

            point = [point_prev[0] + (point_next[0] - point_prev[0]) * dx,
                     point_prev[1] + (point_next[1] - point_prev[1]) * dx]

        point_distance = location.geographic_distance(
            Location(latitude=point[1], longitude=point[0]))

        if point_distance > max_distance:
            continue

        flights.append(flight)

        # limit to 5 flights
        if len(flights) == 5:
            break

    return flights
Beispiel #12
0
def _distance_flight(user, distance, schema):
    flight = Flight.query() \
        .filter(Flight.pilot == user) \
        .filter(Flight.olc_classic_distance >= distance) \
        .order_by(Flight.landing_time) \
        .filter(Flight.is_rankable()) \
        .first()

    if flight:
        return schema.dump(flight).data
Beispiel #13
0
def flights_js():
    flights = Flight.query() \
                    .filter(Flight.is_rankable())

    # Filter by user
    user_id = request.values.get('user', type=int)
    if user_id:
        user = User.get(user_id)
        if not user:
            abort(404)

        flights = flights.filter(
            or_(Flight.pilot == user, Flight.co_pilot == user))

    # Filter by club
    club_id = request.values.get('club', type=int)
    if club_id:
        club = Club.get(club_id)
        if not club:
            abort(404)

        flights = flights.filter(Flight.club == club)

    # Order by date and distance
    flights = flights.order_by(Flight.date_local.desc(),
                               Flight.olc_classic_distance)

    # Limit number of flights
    limit = request.values.get('limit', type=int, default=5)
    if not 0 < limit <= 30:
        raise BadRequest('The `limit` parameter must be between 1 and 30.')

    flights = flights.limit(limit)

    # Produce JS response
    callback = request.values.get('callback', 'onFlightsLoaded')
    return wrap(callback,
                render_template('widgets/flights.jinja', flights=flights))
Beispiel #14
0
def flights_js():
    flights = Flight.query() \
                    .filter(Flight.is_rankable())

    # Filter by user
    user_id = request.values.get('user', type=int)
    if user_id:
        user = User.get(user_id)
        if not user:
            abort(404)

        flights = flights.filter(or_(
            Flight.pilot == user,
            Flight.co_pilot == user
        ))

    # Filter by club
    club_id = request.values.get('club', type=int)
    if club_id:
        club = Club.get(club_id)
        if not club:
            abort(404)

        flights = flights.filter(Flight.club == club)

    # Order by date and distance
    flights = flights.order_by(Flight.date_local.desc(), Flight.olc_classic_distance)

    # Limit number of flights
    limit = request.values.get('limit', type=int, default=5)
    if not 0 < limit <= 30:
        raise BadRequest('The `limit` parameter must be between 1 and 30.')

    flights = flights.limit(limit)

    # Produce JS response
    callback = request.values.get('callback', 'onFlightsLoaded')
    return wrap(callback, render_template('widgets/flights.jinja', flights=flights))
Beispiel #15
0
        g.user.password = data['password']
        g.user.recover_key = None

    if 'club_id' in data and data['club_id'] != g.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

        create_club_join_event(club_id, g.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)))
        for flight in flights:
            flight.club_id = club_id

    db.session.commit()

    return jsonify()


@settings_blueprint.route('/profile')
def profile():
    return render_template('ember-page.jinja', active_page='settings')
Beispiel #16
0
        current_user.password = data['password']
        current_user.recover_key = None

    if 'club_id' in data and data['club_id'] != 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

        current_user.club_id = club_id

        create_club_join_event(club_id, 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 == current_user.id,
                    IGCFile.owner_id == current_user.id)))
        for flight in flights:
            flight.club_id = club_id

    db.session.commit()

    return jsonify()


@settings_blueprint.route('/settings/password/check', methods=['POST'])
@oauth.required()
Beispiel #17
0
        data = FlightSchema(partial=True).load(json, many=True).data
    except ValidationError, e:
        return jsonify(error='validation-failed', fields=e.messages), 422

    ids = [it.get('id') for it in data]
    if not all(ids):
        return jsonify(error='id-missing'), 422

    user_ids = [it['pilot_id'] for it in data if 'pilot_id' in it]
    user_ids.extend([it['co_pilot_id'] for it in data if 'co_pilot_id' in it])

    model_ids = [it['model_id'] for it in data if 'model_id' in it]

    flights = {
        flight.id: flight
        for flight in Flight.query().filter(Flight.id.in_(ids)).all()
    }
    users = {
        user.id: user
        for user in User.query().filter(User.id.in_(user_ids)).all()
    }
    models = {
        model.id: model
        for model in AircraftModel.query().filter(
            AircraftModel.id.in_(model_ids)).all()
    }

    for d in data:
        flight = flights.get(d.pop('id'))
        if not flight or not flight.is_writable(current_user):
            return jsonify(error='unknown-flight'), 422
Beispiel #18
0
def verify():
    current_user = User.get(request.user_id)

    json = request.get_json()
    if json is None:
        return jsonify(error="invalid-request"), 400

    try:
        data = FlightSchema(partial=True).load(json, many=True).data
    except ValidationError as e:
        return jsonify(error="validation-failed", fields=e.messages), 422

    ids = [it.get("id") for it in data]
    if not all(ids):
        return jsonify(error="id-missing"), 422

    user_ids = [it["pilot_id"] for it in data if "pilot_id" in it]
    user_ids.extend([it["co_pilot_id"] for it in data if "co_pilot_id" in it])

    model_ids = [it["model_id"] for it in data if "model_id" in it]

    flights = {
        flight.id: flight for flight in Flight.query().filter(Flight.id.in_(ids)).all()
    }
    users = {user.id: user for user in User.query().filter(User.id.in_(user_ids)).all()}
    models = {
        model.id: model
        for model in AircraftModel.query().filter(AircraftModel.id.in_(model_ids)).all()
    }

    for d in data:
        flight = flights.get(d.pop("id"))
        if not flight or not flight.is_writable(current_user):
            return jsonify(error="unknown-flight"), 422

        if "pilot_id" in d and d["pilot_id"] is not None and d["pilot_id"] not in users:
            return jsonify(error="unknown-pilot"), 422

        if (
            "co_pilot_id" in d
            and d["co_pilot_id"] is not None
            and d["co_pilot_id"] not in users
        ):
            return jsonify(error="unknown-copilot"), 422

        if (
            "model_id" in d
            and d["model_id"] is not None
            and d["model_id"] not in models
        ):
            return jsonify(error="unknown-aircraft-model"), 422

        for key in (
            "takeoff_time",
            "scoring_start_time",
            "scoring_end_time",
            "landing_time",
        ):
            if key in d:
                d[key] = d[key].replace(tzinfo=None)

        old_pilot = flight.pilot_id

        for key, value in d.items():
            setattr(flight, key, value)

        if not (
            flight.takeoff_time
            <= flight.scoring_start_time
            <= flight.scoring_end_time
            <= flight.landing_time
        ):
            return jsonify(error="invalid-times"), 422

        if flight.pilot_id != old_pilot and flight.pilot_id:
            flight.club_id = users[flight.pilot_id].club_id

        flight.privacy_level = Flight.PrivacyLevel.PUBLIC
        flight.time_modified = datetime.utcnow()

    db.session.commit()

    for flight_id in flights.keys():
        try:
            tasks.analyse_flight.delay(flight_id)
            tasks.find_meetings.delay(flight_id)
        except ConnectionError:
            current_app.logger.info("Cannot connect to Redis server")

    return jsonify()
Beispiel #19
0
def update():
    current_user = User.get(request.user_id)
    if not current_user:
        return jsonify(error='invalid-token'), 401

    json = request.get_json()
    if json is None:
        return jsonify(error='invalid-request'), 400

    try:
        data = CurrentUserSchema(partial=True).load(json).data
    except ValidationError as e:
        return jsonify(error='validation-failed', fields=e.messages), 422

    if 'email_address' in data:
        email = data.get('email_address')

        if email != current_user.email_address and User.exists(
                email_address=email):
            return jsonify(error='email-exists-already'), 422

        current_user.email_address = email

    if 'first_name' in data:
        current_user.first_name = data.get('first_name')

    if 'last_name' in data:
        current_user.last_name = data.get('last_name')

    if 'distance_unit' in data:
        current_user.distance_unit = data.get('distance_unit')

    if 'speed_unit' in data:
        current_user.speed_unit = data.get('speed_unit')

    if 'lift_unit' in data:
        current_user.lift_unit = data.get('lift_unit')

    if 'altitude_unit' in data:
        current_user.altitude_unit = data.get('altitude_unit')

    if 'tracking_callsign' in data:
        current_user.tracking_callsign = data.get('tracking_callsign')

    if 'tracking_delay' in data:
        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 current_user.validate_password(data['currentPassword']):
            return jsonify(error='wrong-password'), 403

        current_user.password = data['password']
        current_user.recover_key = None

    if 'club_id' in data and data['club_id'] != 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

        current_user.club_id = club_id

        create_club_join_event(club_id, 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 == current_user.id,
                    IGCFile.owner_id == current_user.id)))
        for flight in flights:
            flight.club_id = club_id

    db.session.commit()

    return jsonify()
Beispiel #20
0
    try:
        data = FlightSchema(partial=True).load(json, many=True).data
    except ValidationError, e:
        return jsonify(error='validation-failed', fields=e.messages), 422

    ids = [it.get('id') for it in data]
    if not all(ids):
        return jsonify(error='id-missing'), 422

    user_ids = [it['pilot_id'] for it in data if 'pilot_id' in it]
    user_ids.extend([it['co_pilot_id'] for it in data if 'co_pilot_id' in it])

    model_ids = [it['model_id'] for it in data if 'model_id' in it]

    flights = {flight.id: flight for flight in Flight.query().filter(Flight.id.in_(ids)).all()}
    users = {user.id: user for user in User.query().filter(User.id.in_(user_ids)).all()}
    models = {model.id: model for model in AircraftModel.query().filter(AircraftModel.id.in_(model_ids)).all()}

    for d in data:
        flight = flights.get(d.pop('id'))
        if not flight or not flight.is_writable(current_user):
            return jsonify(error='unknown-flight'), 422

        if 'pilot_id' in d and d['pilot_id'] is not None and d['pilot_id'] not in users:
            return jsonify(error='unknown-pilot'), 422

        if 'co_pilot_id' in d and d['co_pilot_id'] is not None and d['co_pilot_id'] not in users:
            return jsonify(error='unknown-copilot'), 422

        if 'model_id' in d and d['model_id'] is not None and d['model_id'] not in models:
Beispiel #21
0
parser.add_argument('--force', action='store_true',
                    help='re-analyse all flights, not just the scheduled ones')
parser.add_argument('ids', metavar='ID', nargs='*', type=int,
                    help='Any number of flight IDs.')

args = parser.parse_args()

if not to_envvar(args.config):
    parser.error('Config file "{}" not found.'.format(args.config))


from skylines.model import Flight
from skylines.worker import tasks


def do(flight_id):
    print flight_id
    tasks.analyse_flight.delay(flight_id)


if args.force:
    # invalidate all results
    Flight.query().update({'needs_analysis': True})

if args.ids:
    for flight_id in args.ids:
        do(flight_id)
else:
    for flight in Flight.query(needs_analysis=True):
        do(flight.id)
Beispiel #22
0
parser.add_argument('ids',
                    metavar='ID',
                    nargs='*',
                    type=int,
                    help='Any number of flight IDs.')

args = parser.parse_args()

if not to_envvar(args.config):
    parser.error('Config file "{}" not found.'.format(args.config))

from skylines.model import Flight
from skylines.worker import tasks


def do(flight_id):
    print flight_id
    tasks.analyse_flight.delay(flight_id)


if args.force:
    # invalidate all results
    Flight.query().update({'needs_analysis': True})

if args.ids:
    for flight_id in args.ids:
        do(flight_id)
else:
    for flight in Flight.query(needs_analysis=True):
        do(flight.id)
Beispiel #23
0
def verify():
    current_user = User.get(request.user_id)

    json = request.get_json()
    if json is None:
        return jsonify(error="invalid-request"), 400

    try:
        data = FlightSchema(partial=True).load(json, many=True).data
    except ValidationError as e:
        return jsonify(error="validation-failed", fields=e.messages), 422

    ids = [it.get("id") for it in data]
    if not all(ids):
        return jsonify(error="id-missing"), 422

    user_ids = [it["pilot_id"] for it in data if "pilot_id" in it]
    user_ids.extend([it["co_pilot_id"] for it in data if "co_pilot_id" in it])

    model_ids = [it["model_id"] for it in data if "model_id" in it]

    flights = {
        flight.id: flight for flight in Flight.query().filter(Flight.id.in_(ids)).all()
    }
    users = {user.id: user for user in User.query().filter(User.id.in_(user_ids)).all()}
    models = {
        model.id: model
        for model in AircraftModel.query().filter(AircraftModel.id.in_(model_ids)).all()
    }

    for d in data:
        flight = flights.get(d.pop("id"))
        if not flight or not flight.is_writable(current_user):
            return jsonify(error="unknown-flight"), 422

        if "pilot_id" in d and d["pilot_id"] is not None and d["pilot_id"] not in users:
            return jsonify(error="unknown-pilot"), 422

        if (
            "co_pilot_id" in d
            and d["co_pilot_id"] is not None
            and d["co_pilot_id"] not in users
        ):
            return jsonify(error="unknown-copilot"), 422

        if (
            "model_id" in d
            and d["model_id"] is not None
            and d["model_id"] not in models
        ):
            return jsonify(error="unknown-aircraft-model"), 422

        for key in (
            "takeoff_time",
            "scoring_start_time",
            "scoring_end_time",
            "landing_time",
        ):
            if key in d:
                d[key] = d[key].replace(tzinfo=None)

        old_pilot = flight.pilot_id

        for key, value in d.items():
            setattr(flight, key, value)

        if not (
            flight.takeoff_time
            <= flight.scoring_start_time
            <= flight.scoring_end_time
            <= flight.landing_time
        ):
            return jsonify(error="invalid-times"), 422

        if flight.pilot_id != old_pilot and flight.pilot_id:
            flight.club_id = users[flight.pilot_id].club_id

        flight.privacy_level = Flight.PrivacyLevel.PUBLIC
        flight.time_modified = datetime.utcnow()

    db.session.commit()

    for flight_id in flights.keys():
        try:
            tasks.analyse_flight.delay(flight_id)
            tasks.find_meetings.delay(flight_id)
        except ConnectionError:
            current_app.logger.info("Cannot connect to Redis server")

    return jsonify()
Beispiel #24
0
def update():
    current_user = User.get(request.user_id)
    if not current_user:
        return jsonify(error="invalid-token"), 401

    json = request.get_json()
    if json is None:
        return jsonify(error="invalid-request"), 400

    try:
        data = CurrentUserSchema(partial=True).load(json).data
    except ValidationError as e:
        return jsonify(error="validation-failed", fields=e.messages), 422

    if "email_address" in data:
        email = data.get("email_address")

        if email != current_user.email_address and User.exists(
                email_address=email):
            return jsonify(error="email-exists-already"), 422

        current_user.email_address = email

    if "first_name" in data:
        current_user.first_name = data.get("first_name")

    if "last_name" in data:
        current_user.last_name = data.get("last_name")

    if "distance_unit" in data:
        current_user.distance_unit = data.get("distance_unit")

    if "speed_unit" in data:
        current_user.speed_unit = data.get("speed_unit")

    if "lift_unit" in data:
        current_user.lift_unit = data.get("lift_unit")

    if "altitude_unit" in data:
        current_user.altitude_unit = data.get("altitude_unit")

    if "tracking_callsign" in data:
        current_user.tracking_callsign = data.get("tracking_callsign")

    if "tracking_delay" in data:
        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 current_user.validate_password(data["currentPassword"]):
            return jsonify(error="wrong-password"), 403

        current_user.password = data["password"]
        current_user.recover_key = None

    if "club_id" in data and data["club_id"] != 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

        current_user.club_id = club_id

        create_club_join_event(club_id, 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 == current_user.id,
                    IGCFile.owner_id == current_user.id,
                ),
            ))
        for flight in flights:
            flight.club_id = club_id

    db.session.commit()

    return jsonify()