aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2022-06-29 15:20:34 +0200
committerDaniel Schadt <kingdread@gmx.de>2022-06-29 15:20:34 +0200
commitedb55bf810679d4189b195d621b166c4ca00315e (patch)
treebd66b989e8e4002b1f1fe59aba9fb6a6f7a1b799
parent6de83b043d25fda84b661c47ce5c2ea1c90f6857 (diff)
downloadfietsboek-edb55bf810679d4189b195d621b166c4ca00315e.tar.gz
fietsboek-edb55bf810679d4189b195d621b166c4ca00315e.tar.bz2
fietsboek-edb55bf810679d4189b195d621b166c4ca00315e.zip
add badges
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.mobin3406 -> 4028 bytes
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po102
-rw-r--r--fietsboek/locale/fietslog.pot100
-rw-r--r--fietsboek/models/__init__.py1
-rw-r--r--fietsboek/models/badge.py18
-rw-r--r--fietsboek/models/track.py9
-rw-r--r--fietsboek/models/user.py2
-rw-r--r--fietsboek/routes.py5
-rw-r--r--fietsboek/security.py12
-rw-r--r--fietsboek/static/theme.css19
-rw-r--r--fietsboek/templates/admin.jinja245
-rw-r--r--fietsboek/templates/details.jinja28
-rw-r--r--fietsboek/templates/edit.jinja22
-rw-r--r--fietsboek/templates/edit_form.jinja213
-rw-r--r--fietsboek/templates/finish_upload.jinja22
-rw-r--r--fietsboek/templates/layout.jinja25
-rw-r--r--fietsboek/templates/util.jinja23
-rw-r--r--fietsboek/views/admin.py49
-rw-r--r--fietsboek/views/detail.py7
-rw-r--r--fietsboek/views/edit.py16
-rw-r--r--fietsboek/views/upload.py11
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
index 39dad73..e65c414 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo
Binary files differ
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)