diff options
author | Daniel Schadt <kingdread@gmx.de> | 2022-06-29 15:20:34 +0200 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2022-06-29 15:20:34 +0200 |
commit | edb55bf810679d4189b195d621b166c4ca00315e (patch) | |
tree | bd66b989e8e4002b1f1fe59aba9fb6a6f7a1b799 | |
parent | 6de83b043d25fda84b661c47ce5c2ea1c90f6857 (diff) | |
download | fietsboek-edb55bf810679d4189b195d621b166c4ca00315e.tar.gz fietsboek-edb55bf810679d4189b195d621b166c4ca00315e.tar.bz2 fietsboek-edb55bf810679d4189b195d621b166c4ca00315e.zip |
add badges
-rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.mo | bin | 3406 -> 4028 bytes | |||
-rw-r--r-- | fietsboek/locale/en/LC_MESSAGES/messages.po | 102 | ||||
-rw-r--r-- | fietsboek/locale/fietslog.pot | 100 | ||||
-rw-r--r-- | fietsboek/models/__init__.py | 1 | ||||
-rw-r--r-- | fietsboek/models/badge.py | 18 | ||||
-rw-r--r-- | fietsboek/models/track.py | 9 | ||||
-rw-r--r-- | fietsboek/models/user.py | 2 | ||||
-rw-r--r-- | fietsboek/routes.py | 5 | ||||
-rw-r--r-- | fietsboek/security.py | 12 | ||||
-rw-r--r-- | fietsboek/static/theme.css | 19 | ||||
-rw-r--r-- | fietsboek/templates/admin.jinja2 | 45 | ||||
-rw-r--r-- | fietsboek/templates/details.jinja2 | 8 | ||||
-rw-r--r-- | fietsboek/templates/edit.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/templates/edit_form.jinja2 | 13 | ||||
-rw-r--r-- | fietsboek/templates/finish_upload.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/templates/layout.jinja2 | 5 | ||||
-rw-r--r-- | fietsboek/templates/util.jinja2 | 3 | ||||
-rw-r--r-- | fietsboek/views/admin.py | 49 | ||||
-rw-r--r-- | fietsboek/views/detail.py | 7 | ||||
-rw-r--r-- | fietsboek/views/edit.py | 16 | ||||
-rw-r--r-- | fietsboek/views/upload.py | 11 |
21 files changed, 368 insertions, 61 deletions
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo Binary files differindex 39dad73..e65c414 100644 --- a/fietsboek/locale/en/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po index 695b8ac..1696bec 100644 --- a/fietsboek/locale/en/LC_MESSAGES/messages.po +++ b/fietsboek/locale/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-06-29 13:48+0200\n" +"POT-Creation-Date: 2022-06-29 15:17+0200\n" "PO-Revision-Date: 2022-06-28 13:11+0200\n" "Last-Translator: \n" "Language: en\n" @@ -66,96 +66,124 @@ msgstr "November" msgid "month.december" msgstr "December" -#: fietsboek/models/track.py:218 +#: fietsboek/models/track.py:227 msgid "tooltip.table.length" msgstr "Length" -#: fietsboek/models/track.py:219 +#: fietsboek/models/track.py:228 msgid "tooltip.table.uphill" msgstr "Uphill" -#: fietsboek/models/track.py:220 +#: fietsboek/models/track.py:229 msgid "tooltip.table.downhill" msgstr "Downhill" -#: fietsboek/models/track.py:221 +#: fietsboek/models/track.py:230 msgid "tooltip.table.moving_time" msgstr "Moving Time" -#: fietsboek/models/track.py:222 +#: fietsboek/models/track.py:231 msgid "tooltip.table.stopped_time" msgstr "Stopped Time" -#: fietsboek/models/track.py:223 +#: fietsboek/models/track.py:232 msgid "tooltip.table.max_speed" msgstr "Max Speed" -#: fietsboek/models/track.py:224 +#: fietsboek/models/track.py:233 msgid "tooltip.table.avg_speed" msgstr "Average Speed" -#: fietsboek/templates/details.jinja2:5 +#: fietsboek/templates/admin.jinja2:5 +msgid "page.admin.title" +msgstr "Administration" + +#: fietsboek/templates/admin.jinja2:7 +msgid "page.admin.badges" +msgstr "Badges" + +#: fietsboek/templates/admin.jinja2:22 +msgid "page.admin.badge.edit" +msgstr "Edit" + +#: fietsboek/templates/admin.jinja2:27 +msgid "page.admin.badge.delete_badge" +msgstr "Delete badge" + +#: fietsboek/templates/admin.jinja2:35 +msgid "page.admin.badges.badge_title" +msgstr "Badge Title" + +#: fietsboek/templates/admin.jinja2:39 +msgid "page.admin.badges.badge_image" +msgstr "Badge Image" + +#: fietsboek/templates/admin.jinja2:42 +msgid "page.admin.badges.add_badge" +msgstr "Add Badge" + +#: fietsboek/templates/details.jinja2:6 msgid "page.details.title" msgstr "Track Details" -#: fietsboek/templates/details.jinja2:5 +#: fietsboek/templates/details.jinja2:6 msgid "page.details.edit" msgstr "Edit" -#: fietsboek/templates/details.jinja2:15 +#: fietsboek/templates/details.jinja2:16 msgid "page.details.tags" msgstr "Tagged as" -#: fietsboek/templates/details.jinja2:19 fietsboek/templates/edit.jinja2:9 +#: fietsboek/templates/details.jinja2:20 fietsboek/templates/edit.jinja2:9 #: fietsboek/templates/finish_upload.jinja2:9 msgid "page.noscript" msgstr "JavaScript is disabled, please enable JavaScript" -#: fietsboek/templates/details.jinja2:24 +#: fietsboek/templates/details.jinja2:25 msgid "page.details.download" -msgstr "Download tour" +msgstr "Download Tour" -#: fietsboek/templates/details.jinja2:29 +#: fietsboek/templates/details.jinja2:30 msgid "page.details.date" msgstr "Date" -#: fietsboek/templates/details.jinja2:33 +#: fietsboek/templates/details.jinja2:34 msgid "page.details.start_time" msgstr "Record Start" -#: fietsboek/templates/details.jinja2:37 +#: fietsboek/templates/details.jinja2:38 msgid "page.details.end_time" msgstr "Record End" -#: fietsboek/templates/details.jinja2:41 +#: fietsboek/templates/details.jinja2:42 msgid "page.details.length" msgstr "Length" -#: fietsboek/templates/details.jinja2:45 +#: fietsboek/templates/details.jinja2:46 msgid "page.details.uphill" msgstr "Uphill" -#: fietsboek/templates/details.jinja2:49 +#: fietsboek/templates/details.jinja2:50 msgid "page.details.downhill" msgstr "Downhill" -#: fietsboek/templates/details.jinja2:53 +#: fietsboek/templates/details.jinja2:54 msgid "page.details.moving_time" msgstr "Moving Time" -#: fietsboek/templates/details.jinja2:57 +#: fietsboek/templates/details.jinja2:58 msgid "page.details.stopped_time" msgstr "Stopped Time" -#: fietsboek/templates/details.jinja2:61 +#: fietsboek/templates/details.jinja2:62 msgid "page.details.max_speed" msgstr "Max Speed" -#: fietsboek/templates/details.jinja2:65 +#: fietsboek/templates/details.jinja2:66 msgid "page.details.avg_speed" msgstr "Average Speed" -#: fietsboek/templates/details.jinja2:74 +#: fietsboek/templates/details.jinja2:82 msgid "page.details.comments" msgstr "Comments" @@ -188,6 +216,10 @@ msgid "page.track.form.add_tag" msgstr "Add Tag" #: fietsboek/templates/edit_form.jinja2:28 +msgid "page.track.form.badges" +msgstr "Badges" + +#: fietsboek/templates/edit_form.jinja2:39 msgid "page.track.form.description" msgstr "Description" @@ -233,6 +265,10 @@ msgstr "Logout" msgid "page.navbar.upload" msgstr "Upload" +#: fietsboek/templates/layout.jinja2:52 +msgid "page.navbar.admin" +msgstr "Admin" + #: fietsboek/templates/login.jinja2:5 msgid "page.login.title" msgstr "Login" @@ -253,6 +289,18 @@ msgstr "Login" msgid "page.upload.form.gpx" msgstr "GPX file" +#: fietsboek/views/admin.py:26 +msgid "flash.badge_added" +msgstr "Badge has been added" + +#: fietsboek/views/admin.py:39 +msgid "flash.badge_modified" +msgstr "Badge has been modified" + +#: fietsboek/views/admin.py:48 +msgid "flash.badge_deleted" +msgstr "Badge has been deleted" + #: fietsboek/views/default.py:46 msgid "flash.invalid_credentials" msgstr "Invalid login credentials" @@ -273,11 +321,11 @@ msgstr "No file selected" msgid "flash.invalid_file" msgstr "Invalid GPX file selected" -#: fietsboek/views/upload.py:92 +#: fietsboek/views/upload.py:103 msgid "flash.upload_success" msgstr "Upload successful" -#: fietsboek/views/upload.py:103 +#: fietsboek/views/upload.py:114 msgid "flash.upload_cancelled" msgstr "Upload cancelled" diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot index d8e6582..ba33ce6 100644 --- a/fietsboek/locale/fietslog.pot +++ b/fietsboek/locale/fietslog.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-06-29 13:48+0200\n" +"POT-Creation-Date: 2022-06-29 15:17+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -65,96 +65,124 @@ msgstr "" msgid "month.december" msgstr "" -#: fietsboek/models/track.py:218 +#: fietsboek/models/track.py:227 msgid "tooltip.table.length" msgstr "" -#: fietsboek/models/track.py:219 +#: fietsboek/models/track.py:228 msgid "tooltip.table.uphill" msgstr "" -#: fietsboek/models/track.py:220 +#: fietsboek/models/track.py:229 msgid "tooltip.table.downhill" msgstr "" -#: fietsboek/models/track.py:221 +#: fietsboek/models/track.py:230 msgid "tooltip.table.moving_time" msgstr "" -#: fietsboek/models/track.py:222 +#: fietsboek/models/track.py:231 msgid "tooltip.table.stopped_time" msgstr "" -#: fietsboek/models/track.py:223 +#: fietsboek/models/track.py:232 msgid "tooltip.table.max_speed" msgstr "" -#: fietsboek/models/track.py:224 +#: fietsboek/models/track.py:233 msgid "tooltip.table.avg_speed" msgstr "" -#: fietsboek/templates/details.jinja2:5 +#: fietsboek/templates/admin.jinja2:5 +msgid "page.admin.title" +msgstr "" + +#: fietsboek/templates/admin.jinja2:7 +msgid "page.admin.badges" +msgstr "" + +#: fietsboek/templates/admin.jinja2:22 +msgid "page.admin.badge.edit" +msgstr "" + +#: fietsboek/templates/admin.jinja2:27 +msgid "page.admin.badge.delete_badge" +msgstr "" + +#: fietsboek/templates/admin.jinja2:35 +msgid "page.admin.badges.badge_title" +msgstr "" + +#: fietsboek/templates/admin.jinja2:39 +msgid "page.admin.badges.badge_image" +msgstr "" + +#: fietsboek/templates/admin.jinja2:42 +msgid "page.admin.badges.add_badge" +msgstr "" + +#: fietsboek/templates/details.jinja2:6 msgid "page.details.title" msgstr "" -#: fietsboek/templates/details.jinja2:5 +#: fietsboek/templates/details.jinja2:6 msgid "page.details.edit" msgstr "" -#: fietsboek/templates/details.jinja2:15 +#: fietsboek/templates/details.jinja2:16 msgid "page.details.tags" msgstr "" -#: fietsboek/templates/details.jinja2:19 fietsboek/templates/edit.jinja2:9 +#: fietsboek/templates/details.jinja2:20 fietsboek/templates/edit.jinja2:9 #: fietsboek/templates/finish_upload.jinja2:9 msgid "page.noscript" msgstr "" -#: fietsboek/templates/details.jinja2:24 +#: fietsboek/templates/details.jinja2:25 msgid "page.details.download" msgstr "" -#: fietsboek/templates/details.jinja2:29 +#: fietsboek/templates/details.jinja2:30 msgid "page.details.date" msgstr "" -#: fietsboek/templates/details.jinja2:33 +#: fietsboek/templates/details.jinja2:34 msgid "page.details.start_time" msgstr "" -#: fietsboek/templates/details.jinja2:37 +#: fietsboek/templates/details.jinja2:38 msgid "page.details.end_time" msgstr "" -#: fietsboek/templates/details.jinja2:41 +#: fietsboek/templates/details.jinja2:42 msgid "page.details.length" msgstr "" -#: fietsboek/templates/details.jinja2:45 +#: fietsboek/templates/details.jinja2:46 msgid "page.details.uphill" msgstr "" -#: fietsboek/templates/details.jinja2:49 +#: fietsboek/templates/details.jinja2:50 msgid "page.details.downhill" msgstr "" -#: fietsboek/templates/details.jinja2:53 +#: fietsboek/templates/details.jinja2:54 msgid "page.details.moving_time" msgstr "" -#: fietsboek/templates/details.jinja2:57 +#: fietsboek/templates/details.jinja2:58 msgid "page.details.stopped_time" msgstr "" -#: fietsboek/templates/details.jinja2:61 +#: fietsboek/templates/details.jinja2:62 msgid "page.details.max_speed" msgstr "" -#: fietsboek/templates/details.jinja2:65 +#: fietsboek/templates/details.jinja2:66 msgid "page.details.avg_speed" msgstr "" -#: fietsboek/templates/details.jinja2:74 +#: fietsboek/templates/details.jinja2:82 msgid "page.details.comments" msgstr "" @@ -187,6 +215,10 @@ msgid "page.track.form.add_tag" msgstr "" #: fietsboek/templates/edit_form.jinja2:28 +msgid "page.track.form.badges" +msgstr "" + +#: fietsboek/templates/edit_form.jinja2:39 msgid "page.track.form.description" msgstr "" @@ -232,6 +264,10 @@ msgstr "" msgid "page.navbar.upload" msgstr "" +#: fietsboek/templates/layout.jinja2:52 +msgid "page.navbar.admin" +msgstr "" + #: fietsboek/templates/login.jinja2:5 msgid "page.login.title" msgstr "" @@ -252,6 +288,18 @@ msgstr "" msgid "page.upload.form.gpx" msgstr "" +#: fietsboek/views/admin.py:26 +msgid "flash.badge_added" +msgstr "" + +#: fietsboek/views/admin.py:39 +msgid "flash.badge_modified" +msgstr "" + +#: fietsboek/views/admin.py:48 +msgid "flash.badge_deleted" +msgstr "" + #: fietsboek/views/default.py:46 msgid "flash.invalid_credentials" msgstr "" @@ -272,11 +320,11 @@ msgstr "" msgid "flash.invalid_file" msgstr "" -#: fietsboek/views/upload.py:92 +#: fietsboek/views/upload.py:103 msgid "flash.upload_success" msgstr "" -#: fietsboek/views/upload.py:103 +#: fietsboek/views/upload.py:114 msgid "flash.upload_cancelled" msgstr "" diff --git a/fietsboek/models/__init__.py b/fietsboek/models/__init__.py index 4d8942c..3f07926 100644 --- a/fietsboek/models/__init__.py +++ b/fietsboek/models/__init__.py @@ -6,6 +6,7 @@ import zope.sqlalchemy # Import or define all models here to ensure they are attached to the # ``Base.metadata`` prior to any initialization routines. from .user import User # flake8: noqa +from .badge import Badge # flake8: noqa from .track import Track, TrackCache, Upload # flake8: noqa # Run ``configure_mappers`` after defining all of the models to ensure diff --git a/fietsboek/models/badge.py b/fietsboek/models/badge.py new file mode 100644 index 0000000..42094d9 --- /dev/null +++ b/fietsboek/models/badge.py @@ -0,0 +1,18 @@ +from sqlalchemy import ( + Column, + Integer, + Text, + LargeBinary, +) +from sqlalchemy.orm import relationship + +from .meta import Base + + +class Badge(Base): + __tablename__ = 'badges' + id = Column(Integer, primary_key=True) + title = Column(Text) + image = Column(LargeBinary) + + tracks = relationship('Track', secondary='track_badge_assoc', back_populates='badges') diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py index d2b43d5..d264e15 100644 --- a/fietsboek/models/track.py +++ b/fietsboek/models/track.py @@ -53,6 +53,14 @@ track_people_assoc = Table( ) +track_badge_assoc = Table( + "track_badge_assoc", + Base.metadata, + Column("track_id", ForeignKey("tracks.id"), primary_key=True), + Column("badge_id", ForeignKey("badges.id"), primary_key=True), +) + + class Track(Base): __tablename__ = 'tracks' id = Column(Integer, primary_key=True) @@ -67,6 +75,7 @@ class Track(Base): owner = relationship('User', back_populates='tracks') cache = relationship('TrackCache', back_populates='track', uselist=False) tagged_people = relationship('User', secondary=track_people_assoc, back_populates='tagged_tracks') + badges = relationship('Badge', secondary=track_badge_assoc, back_populates='tracks') # GPX files are XML files with a lot of repeated property names. Really, it # is quite inefficient to store a whole ton of GPS points in big XML diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py index 52bb6db..9bfcdc4 100644 --- a/fietsboek/models/user.py +++ b/fietsboek/models/user.py @@ -4,6 +4,7 @@ from sqlalchemy import ( Integer, Text, LargeBinary, + Boolean, ) from sqlalchemy.orm import relationship @@ -41,6 +42,7 @@ class User(Base): password = Column(LargeBinary) salt = Column(LargeBinary) email = Column(Text) + is_admin = Column(Boolean) tracks = relationship('Track', back_populates='owner') tagged_tracks = relationship('Track', secondary='track_people_assoc', back_populates='tagged_people') diff --git a/fietsboek/routes.py b/fietsboek/routes.py index 9f57adc..ef4fdd0 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -10,3 +10,8 @@ def includeme(config): config.add_route('details', '/track/{id}') config.add_route('edit', '/edit/{id}') config.add_route('gpx', '/gpx/{id}.gpx') + config.add_route('badge', '/badge/{id}') + config.add_route('admin', '/admin') + config.add_route('admin-badge-add', '/admin/add-badge') + config.add_route('admin-badge-edit', '/admin/edit-badge') + config.add_route('admin-badge-delete', '/admin/delete-badge') diff --git a/fietsboek/security.py b/fietsboek/security.py index 3f27d6b..1ebf7a4 100644 --- a/fietsboek/security.py +++ b/fietsboek/security.py @@ -7,6 +7,9 @@ from . import models from sqlalchemy import select +ADMIN_PERMISSIONS = {'admin'} + + class SecurityPolicy: def __init__(self): self.helper = SessionAuthenticationHelper() @@ -30,10 +33,13 @@ class SecurityPolicy: def permits(self, request, context, permission): """ Allow access to everything if signed in. """ identity = self.identity(request) - if identity is not None: - return Allowed('User is signed in.') - else: + if identity is None: return Denied('User is not signed in.') + if permission not in ADMIN_PERMISSIONS: + return Allowed('User is signed in.') + if request.identity.is_admin: + return Allowed('User is an administrator.') + return Denied('User is not an administrator.') def remember(self, request, userid, **kw): return self.helper.remember(request, userid, **kw) diff --git a/fietsboek/static/theme.css b/fietsboek/static/theme.css index 274b030..d373c93 100644 --- a/fietsboek/static/theme.css +++ b/fietsboek/static/theme.css @@ -5,6 +5,25 @@ body { background: #efefef; } +.badge-container { + width: 50px; + height: 50px; + border: 1px solid #dee2e6; + display: flex; + justify-content: center; + align-items: center; +} + +.admin-badge-list * { + margin-left: 5px; + margin-right: 5px; +} + +.badge-container img { + max-width: 100%; + max-height: 100%; +} + .tooltip-inner { max-width: 400px; } diff --git a/fietsboek/templates/admin.jinja2 b/fietsboek/templates/admin.jinja2 new file mode 100644 index 0000000..a347ebf --- /dev/null +++ b/fietsboek/templates/admin.jinja2 @@ -0,0 +1,45 @@ +{% extends "layout.jinja2" %} +{% import "util.jinja2" as util %} +{% block content %} +<div class="container"> + <h1>{{ _("page.admin.title") }}</h1> + + <h2>{{ _("page.admin.badges") }}</h2> + + <div class="list-group"> + {% for badge in badges %} + <span href="#" class="list-group-item list-group-item-action d-flex admin-badge-list"> + {{ util.render_badge(request, badge) }} + <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-edit') }}"> + <input type="hidden" name="badge-edit-id" value="{{ badge.id }}"> + <div class="mb-3"> + <input type="text" class="form-control" name="badge-title" value="{{ badge.title }}"> + </div> + <div class="mb-3"> + <input class="form-control" type="file" name="badge-image"> + </div> + <div class="mb-3"> + <button class="btn btn-primary">{{ _("page.admin.badge.edit") }}</button> + </div> + </form> + <form method="POST" action="{{ request.route_path('admin-badge-delete') }}"> + <input type="hidden" name="badge-delete-id" value="{{ badge.id }}"> + <button class="btn btn-danger">{{ _("page.admin.badge.delete_badge") }}</button> + </form> + </span> + {% endfor %} + </div> + + <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-add') }}"> + <div class="mb-3"> + <label for="badge-title" class="form-label">{{ _("page.admin.badges.badge_title") }}</label> + <input type="text" class="form-control" id="badge-title" name="badge-title"> + </div> + <div class="mb-3"> + <label for="badge-image" class="form-label">{{ _("page.admin.badges.badge_image") }}</label> + <input class="form-control" type="file" name="badge-image"> + </div> + <button type="submit" class="btn btn-primary">{{ _("page.admin.badges.add_badge") }}</button> + </form> +</div> +{% endblock %} diff --git a/fietsboek/templates/details.jinja2 b/fietsboek/templates/details.jinja2 index f4ed75a..817d9b2 100644 --- a/fietsboek/templates/details.jinja2 +++ b/fietsboek/templates/details.jinja2 @@ -1,4 +1,5 @@ {% extends "layout.jinja2" %} +{% import "util.jinja2" as util %} {% block content %} <div class="container"> @@ -67,6 +68,13 @@ </tr> </tbody> </table> + {% if track.badges %} + <div class="d-flex"> + {% for badge in track.badges %} + {{ util.render_badge(request, badge) }} + {% endfor %} + </div> + {% endif %} {% if description %} {{ description }} <hr> diff --git a/fietsboek/templates/edit.jinja2 b/fietsboek/templates/edit.jinja2 index a4147fe..a1985e6 100644 --- a/fietsboek/templates/edit.jinja2 +++ b/fietsboek/templates/edit.jinja2 @@ -9,7 +9,7 @@ <noscript><p>{{ _("page.noscript") }}<p></noscript> </div> <form method="POST"> - {{ edit_form.edit_track(track.title, track.date, track.description, track.tags) }} + {{ edit_form.edit_track(track.title, track.date, track.description, track.tags, badges) }} <button type="submit" class="btn btn-primary">{{ _("page.edit.form.submit") }}</button> <a href="{{ request.route_url('details', id=track.id) }}" class="btn btn-secondary">{{ _("page.edit.form.cancel") }}</a> </form> diff --git a/fietsboek/templates/edit_form.jinja2 b/fietsboek/templates/edit_form.jinja2 index 6c30c5b..e69fe2b 100644 --- a/fietsboek/templates/edit_form.jinja2 +++ b/fietsboek/templates/edit_form.jinja2 @@ -1,4 +1,4 @@ -{% macro edit_track(title, date, description, tags) %} +{% macro edit_track(title, date, description, tags, badges) %} <div class="mb-3"> <label for="formTitle" class="form-label">{{ _("page.track.form.title") }}</label> <input class="form-control" type="text" id="formTitle" name="title" value="{{ title | default("", true) }}"> @@ -25,6 +25,17 @@ <input type="hidden" name="tags" id="formTagsInput"> </div> <div class="mb-3"> + <div>{{ _("page.track.form.badges") }}</div> + {% for (state, badge) in badges %} + <div class="form-check"> + <input class="form-check-input" type="checkbox" value="marked" id="badge-{{ badge.id }}" name="badge-{{ badge.id }}"{% if state %} checked{% endif %}> + <label class="form-check-label" for="badge-{{ badge.id }}"> + {{ badge.title }} + </label> + </div> + {% endfor %} +</div> +<div class="mb-3"> <label for="formDesc" class="form-label">{{ _("page.track.form.description") }}</label> <textarea class="form-control" id="formDesc" name="description">{{ description | default("", true) }}</textarea> </div> diff --git a/fietsboek/templates/finish_upload.jinja2 b/fietsboek/templates/finish_upload.jinja2 index bb10a8b..02906a2 100644 --- a/fietsboek/templates/finish_upload.jinja2 +++ b/fietsboek/templates/finish_upload.jinja2 @@ -9,7 +9,7 @@ <noscript><p>{{ _("page.noscript") }}<p></noscript> </div> <form method="POST"> - {{ edit_form.edit_track(upload_title, upload_date, upload_description, upload_tags) }} + {{ edit_form.edit_track(upload_title, upload_date, upload_description, upload_tags, badges) }} <button type="submit" class="btn btn-primary">{{ _("page.upload.form.submit") }}</button> <a href="{{ request.route_url('cancel-upload', id=preview_id) }}" class="btn btn-danger">{{ _("page.upload.form.cancel") }}</a> </form> diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index 2515f01..4ab8b42 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -47,6 +47,11 @@ <li class="nav-item"> <a class="nav-link" href="{{ request.route_url('upload') }}">{{ _("page.navbar.upload") }}</a> </li> + {% if request.identity.is_admin %} + <li class="nav-item"> + <a class="nav-link" href="{{ request.route_url('admin') }}">{{ _("page.navbar.admin") }}</a> + </li> + {% endif %} {% endif %} </ul> </div> diff --git a/fietsboek/templates/util.jinja2 b/fietsboek/templates/util.jinja2 new file mode 100644 index 0000000..9fbf0ec --- /dev/null +++ b/fietsboek/templates/util.jinja2 @@ -0,0 +1,3 @@ +{% macro render_badge(request, badge) -%} +<div class="badge-container"><img src="{{ request.route_url('badge', id=badge.id) }}" data-bs-toggle="tooltip" title="{{ badge.title }}"></div> +{%- endmacro %} diff --git a/fietsboek/views/admin.py b/fietsboek/views/admin.py new file mode 100644 index 0000000..35b73be --- /dev/null +++ b/fietsboek/views/admin.py @@ -0,0 +1,49 @@ +from pyramid.view import view_config +from pyramid.httpexceptions import HTTPFound +from pyramid.i18n import TranslationString as _ + +from sqlalchemy import select + +from .. import models + + +@view_config(route_name='admin', renderer='fietsboek:templates/admin.jinja2', request_method="GET", permission="admin") +def admin(request): + badges = request.dbsession.execute(select(models.Badge)).scalars() + return { + 'badges': badges, + } + + +@view_config(route_name='admin-badge-add', permission="admin", request_method="POST") +def do_badge_add(request): + image = request.params['badge-image'].file.read() + title = request.params['badge-title'] + + badge = models.Badge(title=title, image=image) + request.dbsession.add(badge) + + request.session.flash(request.localizer.translate(_("flash.badge_added"))) + return HTTPFound(request.route_url('admin')) + + +@view_config(route_name='admin-badge-edit', permission="admin", request_method="POST") +def do_badge_edit(request): + badge = request.dbsession.execute(select(models.Badge).filter_by(id=request.params["badge-edit-id"])).scalar_one() + try: + badge.image = request.params['badge-image'].file.read() + except AttributeError: + pass + badge.title = request.params['badge-title'] + + request.session.flash(request.localizer.translate(_("flash.badge_modified"))) + return HTTPFound(request.route_url('admin')) + + +@view_config(route_name='admin-badge-delete', permission="admin", request_method="POST") +def do_badge_delete(request): + badge = request.dbsession.execute(select(models.Badge).filter_by(id=request.params["badge-delete-id"])).scalar_one() + request.dbsession.delete(badge) + + request.session.flash(request.localizer.translate(_("flash.badge_deleted"))) + return HTTPFound(request.route_url('admin')) diff --git a/fietsboek/views/detail.py b/fietsboek/views/detail.py index ce14af2..eadc76f 100644 --- a/fietsboek/views/detail.py +++ b/fietsboek/views/detail.py @@ -23,3 +23,10 @@ def gpx(request): query = select(models.Track).filter_by(id=request.matchdict["id"]) track = request.dbsession.execute(query).scalar_one() return Response(track.gpx_data, content_type="application/gpx+xml") + + +@view_config(route_name='badge') +def badge(request): + query = select(models.Badge).filter_by(id=request.matchdict["id"]) + badge = request.dbsession.execute(query).scalar_one() + return Response(badge.image) diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py index f5fe8d6..3bd3663 100644 --- a/fietsboek/views/edit.py +++ b/fietsboek/views/edit.py @@ -14,7 +14,12 @@ def edit(request): track = request.dbsession.execute(query).scalar_one() if request.identity != track.owner: return HTTPForbidden() - return {'track': track} + badges = request.dbsession.execute(select(models.Badge)).scalars() + badges = [(badge in track.badges, badge) for badge in badges] + return { + 'track': track, + 'badges': badges, + } @view_config(route_name='edit', permission='edit', request_method='POST') @@ -24,9 +29,16 @@ def do_edit(request): if request.identity != track.owner: return HTTPForbidden() + badges = request.dbsession.execute(select(models.Badge)).scalars() + active_badges = [ + badge for badge in badges + if request.params.get(f'badge-{badge.id}') == 'marked' + ] + track.title = request.params["title"] track.description = request.params["description"] track.tags = set(request.params["tags"].split(" ")) - trackdate = datetime.datetime.fromisoformat(request.params["date"]) + track.date = datetime.datetime.fromisoformat(request.params["date"]) + track.badges = active_badges return HTTPFound(request.route_url('details', id=track.id)) diff --git a/fietsboek/views/upload.py b/fietsboek/views/upload.py index 3ef0252..1494dbc 100644 --- a/fietsboek/views/upload.py +++ b/fietsboek/views/upload.py @@ -59,6 +59,9 @@ def finish_upload(request): upload = request.dbsession.execute(query).scalar_one() if upload.owner != request.identity: return HTTPForbidden() + + badges = request.dbsession.execute(select(models.Badge)).scalars() + badges = [(False, badge) for badge in badges] gpx = gpxpy.parse(upload.gpx_data) return { @@ -67,6 +70,7 @@ def finish_upload(request): 'upload_date': gpx.time, 'upload_description': gpx.description, 'upload_tags': set(), + 'badges': badges, } @@ -77,12 +81,19 @@ def do_finish_upload(request): if upload.owner != request.identity: return HTTPForbidden() + badges = request.dbsession.execute(select(models.Badge)).scalars() + active_badges = [ + badge for badge in badges + if request.params.get(f'badge-{badge.id}') == 'marked' + ] + track = models.Track( owner=request.identity, title=request.params["title"], date=datetime.datetime.fromisoformat(request.params["date"]), description=request.params["description"], tags=set(request.params["tags"].split(" ")), + badges=active_badges, ) track.gpx_data = upload.gpx_data request.dbsession.add(track) |