diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2026-11-07 23:52:53 +0100 |
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2026-11-07 23:52:53 +0100 |
| commit | ad3803e12e4ded69ae4bc948bee6e41c5089dbd5 (patch) | |
| tree | 9408cbb7b77e5eacd5c69158fc1a621149c40a7d | |
| parent | 3fb48b99ed6883154f4f0b9e3d3957ec9a23aef1 (diff) | |
| download | fietsboek-ad3803e12e4ded69ae4bc948bee6e41c5089dbd5.tar.gz fietsboek-ad3803e12e4ded69ae4bc948bee6e41c5089dbd5.tar.bz2 fietsboek-ad3803e12e4ded69ae4bc948bee6e41c5089dbd5.zip | |
starting point for climb browserbrowse-climbs
| -rw-r--r-- | asset-sources/theme.scss | 25 | ||||
| -rw-r--r-- | fietsboek/routes.py | 1 | ||||
| -rw-r--r-- | fietsboek/static/theme.css | 20 | ||||
| -rw-r--r-- | fietsboek/static/theme.css.map | 2 | ||||
| -rw-r--r-- | fietsboek/templates/climb_browse.jinja2 | 227 | ||||
| -rw-r--r-- | fietsboek/templates/details.jinja2 | 1 | ||||
| -rw-r--r-- | fietsboek/templates/layout.jinja2 | 3 | ||||
| -rw-r--r-- | fietsboek/views/climb.py | 17 |
8 files changed, 295 insertions, 1 deletions
diff --git a/asset-sources/theme.scss b/asset-sources/theme.scss index fbb836e..684b075 100644 --- a/asset-sources/theme.scss +++ b/asset-sources/theme.scss @@ -241,6 +241,31 @@ strong { } /* </Climbs> */ +/* <Climb-Browse> */ +.climb-browse-card { + .card-body { + padding: 0; + display: grid; + grid-template-areas: + "map stats" + "map profile"; + grid-template-rows: 190px 110px; + } + + .climb-profile { + grid-area: profile; + width: 300px; + height: 110px; + } +} + +.climb-browse-list { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} +/* </Climb-Browse> */ + .count-field { width: 50px; display: inline-block; diff --git a/fietsboek/routes.py b/fietsboek/routes.py index da9ed04..636c62a 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -70,6 +70,7 @@ def includeme(config): "/climb/{climb_id}/profile", factory="fietsboek.models.Climb.factory", ) + config.add_route("climb-browse", "/climb/") config.add_route("journey-list", "/journey/") config.add_route( diff --git a/fietsboek/static/theme.css b/fietsboek/static/theme.css index 6b9e1c2..d1e63fb 100644 --- a/fietsboek/static/theme.css +++ b/fietsboek/static/theme.css @@ -232,6 +232,26 @@ strong { } /* </Climbs> */ +/* <Climb-Browse> */ +.climb-browse-card .card-body { + padding: 0; + display: grid; + grid-template-areas: "map stats" "map profile"; + grid-template-rows: 190px 110px; +} +.climb-browse-card .climb-profile { + grid-area: profile; + width: 300px; + height: 110px; +} + +.climb-browse-list { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +/* </Climb-Browse> */ .count-field { width: 50px; display: inline-block; diff --git a/fietsboek/static/theme.css.map b/fietsboek/static/theme.css.map index 30b9235..b499ab1 100644 --- a/fietsboek/static/theme.css.map +++ b/fietsboek/static/theme.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAlCJ;EAqCE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;;;AAGJ;AAEA;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAKF;EACE;EACA;EAEA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAGF;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;EACA;AACA;AAAA;AAAA;EAGA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"}
\ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAIA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAlCJ;EAqCE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;;;AAGJ;AAEA;AAEE;EACE;EACA;EACA,qBACE;EAEF;;AAGF;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;;AAEF;AAEA;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAKF;EACE;EACA;EAEA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAEF;EACE;;;AAGF;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;EACA;AACA;AAAA;AAAA;EAGA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"}
\ No newline at end of file diff --git a/fietsboek/templates/climb_browse.jinja2 b/fietsboek/templates/climb_browse.jinja2 new file mode 100644 index 0000000..d754c46 --- /dev/null +++ b/fietsboek/templates/climb_browse.jinja2 @@ -0,0 +1,227 @@ +{% extends "layout.jinja2" %} +{% block content %} +<div class="container"> + <h1>{{ _("page.climb_browse.title") }}</h1> + <div class="mb-3"> + <form id="browseFilter"> + <div class="row g-3 mb-3"> + <div class="col-12"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="search-terms" type="text" class="form-control ui-element" placeholder="{{ _("page.browse.filter.search_terms") }}" value="{{ request.params.get('search-terms', '') }}"> + </div> + </div> + </div> + + <div class="collapse row g-3 mb-3" id="advancedSearch"> + <div class="col-md-6"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="tags" type="text" class="form-control ui-element" placeholder="{{ _("page.browse.filter.tags") }}" value="{{ request.params.get('tags', '') }}"> + </div> + </div> + + <div class="col-md-6"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="tagged-person" type="text" class="form-control ui-element" placeholder="{{ _("page.browse.filter.tagged_person") }}" value="{{ request.params.get('tagged-person', '') }}"> + </div> + </div> + + <div class="col-md-4"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="min-length" type="number" class="form-control ui-element" placeholder="{{ _("page.browse.filter.length_minimum") }}" value="{{ request.params.get('min-length', '') }}"> + <span class="input-group-text ui-element">km</span> + </div> + </div> + + <div class="col-md-4 fs-5 d-flex align-items-center justify-content-center ui-element"> + {{ _("page.browse.filter.length_boundaries") }} + </div> + + <div class="col-md-4"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="max-length" type="number" class="form-control ui-element" placeholder="{{ _("page.browse.filter.length_maximum") }}" value="{{ request.params.get('max-length', '') }}"> + <span class="input-group-text ui-element">km</span> + </div> + </div> + + <div class="col-md-4"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="min-uphill" type="number" class="form-control ui-element" placeholder="{{ _("page.browse.filter.uphill_minimum") }}" value="{{ request.params.get('min-uphill', '') }}"> + <span class="input-group-text ui-element">m</span> + </div> + </div> + + <div class="col-md-4 fs-5 d-flex align-items-center justify-content-center ui-element"> + {{ _("page.browse.filter.uphill_boundaries") }} + </div> + + <div class="col-md-4"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="max-uphill" type="number" class="form-control ui-element" placeholder="{{ _("page.browse.filter.uphill_maximum") }}" value="{{ request.params.get('max-uphill', '') }}"> + <span class="input-group-text ui-element">m</span> + </div> + </div> + + <div class="col-md-4"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="min-date" type="date" class="form-control ui-element" value="{{ request.params.get('min-date', '') }}"> + </div> + </div> + + <div class="col-md-4 fs-5 d-flex align-items-center justify-content-center ui-element"> + {{ _("page.browse.filter.date_boundaries") }} + </div> + + <div class="col-md-4"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <input name="max-date" type="date" class="form-control ui-element" value="{{ request.params.get('max-date', '') }}"> + </div> + </div> + + <div class="col-md-6"> + <div class="input-group"> + <button type="button" class="btn btn-outline-secondary button-clear-input"><i class="bi bi-eraser-fill"></i></button> + <select class="form-select ui-element" size="2" name="type[]" multiple> + <option value="ORGANIC"{% if "ORGANIC" in request.params.getall("type[]") %} selected{% endif %}>{{ _("page.browse.filter.type.organic") }}</option> + <option value="SYNTHETIC"{% if "SYNTHETIC" in request.params.getall("type[]") %} selected{% endif %}>{{ _("page.browse.filter.type.synthetic") }}</option> + </select> + </div> + + {% if request.identity %} + <div class="input-group mt-2"> + <button type="button" class="btn btn-outline-secondary button-clear-input ui-element"><i class="bi bi-eraser-fill"></i></button> + <select class="form-select" name="favourite"> + <option value="">{{ _("page.browse.filter.favourite.all") }}</option> + <option value="true"{% if request.params.get("favourite") == "true" %} selected{% endif %}>{{ _("page.browse.filter.favourite.yes") }}</option> + <option value="false"{% if request.params.get("favourite") == "false" %} selected{% endif %}>{{ _("page.browse.filter.favourite.no") }}</option> + </select> + </div> + {% endif %} + </div> + <div class="col-md-6"> + {% if request.identity %} + {% macro render_switch(id, name, value, text) %} + <div class="form-check form-switch ui-element"> + <input class="form-check-input" type="checkbox" role="switch" id="{{ id }}" name="{{ name }}" value="{{ value }}" {% if value in request.params.getall(name) %}checked{% endif %}> + <label class="form-check-label" for="{{ id }}">{{ text }}</label> + </div> + {% endmacro %} + {{ render_switch("switchOnlyMyTracks", "show-only[]", "mine", _("page.browse.filter.my_tracks.only")) }} + {{ render_switch("switchOnlyFriendsTracks", "show-only[]", "friends", _("page.browse.filter.friends_tracks_only")) }} + {{ render_switch("switchOnlyMeTagged", "show-only[]", "user-tagged", _("page.browse.filter.me_tagged_only")) }} + {% endif %} + </div> + + </div> + + <div class="row g-3 mb-3"> + <div class="col"> + <button type="submit" class="btn btn-primary ui-element"> + <i class="bi bi-funnel-fill"></i> + {{ _("page.browse.filters.apply") }} + </button> + <a class="btn btn-info ui-element" href="{{ request.route_path('browse') }}"> + <i class="bi bi-x-octagon"></i> + {{ _("page.browse.filters.clear_all") }} + </a> + <button class="btn btn-secondary ui-element" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSearch" aria-expanded="false" aria-controls="advancedSearch"> + {{ _("page.browse.filters.expand_advanced") }} + </button> + </div> + <div class="col"> + <select class="form-select ui-element" name="sort"> + <option value="DATE_DESC" {% if request.params.get("sort") == "DATE_DESC" %}selected{% endif %}>{{ _("page.browse.sort.date") }} ↓</option> + <option value="DATE_ASC" {% if request.params.get("sort") == "DATE_ASC" %}selected{% endif %}>{{ _("page.browse.sort.date") }} ↑</option> + <option value="LENGTH_DESC" {% if request.params.get("sort") == "LENGTH_DESC" %}selected{% endif %}>{{ _("page.browse.sort.length") }} ↓</option> + <option value="LENGTH_ASC" {% if request.params.get("sort") == "LENGTH_ASC" %}selected{% endif %}>{{ _("page.browse.sort.length") }} ↑</option> + <option value="UPHILL_DESC" {% if request.params.get("sort") == "UPHILL_DESC" %}selected{% endif %}>{{ _("page.browse.sort.uphill") }} ↓</option> + <option value="UPHILL_ASC" {% if request.params.get("sort") == "UPHILL_ASC" %}selected{% endif %}>{{ _("page.browse.sort.uphill") }} ↑</option> + <option value="DURATION_DESC" {% if request.params.get("sort") == "DURATION_DESC" %}selected{% endif %}>{{ _("page.browse.sort.duration") }} ↓</option> + <option value="DURATION_ASC" {% if request.params.get("sort") == "DURATION_ASC" %}selected{% endif %}>{{ _("page.browse.sort.duration") }} ↑</option> + </select> + </div> + </div> + </form> + </div> + {% if climbs %} + <div class="climb-browse-list mb-3"> + {% for climb in climbs %} + <div class="card climb-browse-card"> + <h5 class="card-header"> + <a href="{{ request.route_url('details', track_id=climb.track_id) }}#climb-{{ climb.id }}"> + {{ climb.display_name() }} + </a> + {% if climb.category is not none %} + <div class="climb-category climb-category-{{ climb.category }}"> + {% if climb.category == 0 %} + HC + {% else %} + {{ climb.category }} + {% endif %} + </div> + {% endif %} + </h5> + <div class="card-body"> + <img src="{{ request.route_url('climb-preview', climb_id=climb.id) }}" style="grid-area: map;"> + <table class="table climb-details" style="grid-area: stats;"> + <tr> + <th scope="row">{{ _("page.details.climb.length") }}</th> + <td>{{ (climb.length / 1000) | round(2) | format_decimal }} km</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.climb.average_grade") }}</th> + <td>{{ (climb.avg_grade * 100) | round(2) | format_decimal }} %</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.climb.lowest_elevation") }}</th> + <td>{{ climb.lowest_elevation | round(2) | format_decimal }} m</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.climb.highest_elevation") }}</th> + <td>{{ climb.highest_elevation | round(2) | format_decimal }} m</td> + </tr> + </table> + <img class="climb-profile" src="{{ request.route_url('climb-profile', climb_id=climb.id) }}"> + </div> + </div> + {% endfor %} + </div> + + <nav aria-label="Page navigation"> + <ul class="pagination justify-content-center"> + {% if page_previous is none %} + <li class="page-item disabled"> + <span class="page-link">{{ _("pagination.previous") }}</span> + </li> + {% else %} + <li class="page-item"> + <a class="page-link" href="{{ page_previous | safe }}">{{ _("pagination.previous") }}</a> + </li> + {% endif %} + {% if page_next is none %} + <li class="page-item disabled"> + <span class="page-link">{{ _("pagination.next") }}</span> + </li> + {% else %} + <li class="page-item"> + <a class="page-link" href="{{ page_next | safe }}">{{ _("pagination.next") }}</a> + </li> + {% endif %} + </ul> + </nav> + + {% elif used_filters %} + <p>{{ _("page.climb_browse.no_results") }}</p> + {% else %} + <p>{{ _("page.climb_browse.no_climbs") }}</p> + {% endif %} +</div> +{% endblock %} diff --git a/fietsboek/templates/details.jinja2 b/fietsboek/templates/details.jinja2 index 210e301..0fd7d30 100644 --- a/fietsboek/templates/details.jinja2 +++ b/fietsboek/templates/details.jinja2 @@ -199,6 +199,7 @@ <div class="climbs"> {% for climb in track.climbs %} <div class="climb card"> + <a name="climb-{{ climb.id }}"> <h5 class="card-header"> {{ climb.display_name() | default(_("page.details.climbs.climb"), true) }} {% if climb.category is not none %} diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index 3058553..d7f8086 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -60,6 +60,9 @@ const Legende = false; <li class="nav-item"> <a class="nav-link" href="{{ request.route_url('journey-list') }}">{{ _("page.navbar.journeys") }}</a> </li> + <li class="nav-item"> + <a class="nav-link" href="{{ request.route_url('climb-browse') }}">{{ _("page.navbar.climb-browse") }}</a> + </li> {% if request.identity is not none %} <li class="nav-item"> <a class="nav-link" href="{{ request.route_url('upload') }}">{{ _("page.navbar.upload") }}</a> diff --git a/fietsboek/views/climb.py b/fietsboek/views/climb.py index 1f5641f..e3594ef 100644 --- a/fietsboek/views/climb.py +++ b/fietsboek/views/climb.py @@ -9,11 +9,28 @@ from pyramid.httpexceptions import ( from pyramid.request import Request from pyramid.response import Response from pyramid.view import view_config +from sqlalchemy import select +from sqlalchemy.orm import aliased from .. import models LOGGER = logging.getLogger(__name__) +@view_config( + route_name="climb-browse", + renderer="fietsboek:templates/climb_browse.jinja2", + request_method="GET", +) +def climb_browse(request: Request): + tracks_qry = aliased(models.Track, models.User.visible_tracks_query(request.identity).subquery()) + climbs_qry = select(models.Climb).filter(models.Climb.track_id.in_(select(tracks_qry.id))) + climbs = list(request.dbsession.execute(climbs_qry).scalars()) + return { + "climbs": climbs, + "page_previous": None, + "page_next": None, + } + @view_config(route_name="climb-preview", permission="track.view") def climb_preview(request: Request): |
