diff --git a/src/backoffice/locale/de/LC_MESSAGES/django.po b/src/backoffice/locale/de/LC_MESSAGES/django.po index fe2cb0db938a0fca183f517592fe9de4423e6bf9..c4e059433066244d5286a649776005bf7a24c8cd 100644 --- a/src/backoffice/locale/de/LC_MESSAGES/django.po +++ b/src/backoffice/locale/de/LC_MESSAGES/django.po @@ -240,6 +240,27 @@ msgstr "Redeem Token" msgid "BadgeToken__Badge_back__button %(badge)s" msgstr "Zurück zu %(badge)s" +msgid "BadgeToken__introduction" +msgstr "Mit diesem Token kannst du Teilnehmenden eine Badge verleihen. Dazu hast du mehrere Möglichkeiten. Um die Informationen im aktuellen Design anzuzeigen folge diesem Link:" + +msgid "BadgeToken__redeem__info_link" +msgstr "Auf der Website" + +msgid "BadgeToken__redeem_link_label" +msgstr "Einlöse-Link" + +msgid "BadgeToken__redeem_link__introduction" +msgstr "Mit diesem Link können sich Teilnehmende die Badge holen (wird z.B. auch im QR Code verwendet)" + +msgid "BadgeToken__redeem_token_label" +msgstr "Einlöse-Token" + +msgid "BadgeToken__redeem_token__introduction" +msgstr "Mit diesem Token können sich Benutzer die Badge in ihrem Profile hinzufügen" + +msgid "BadgeToken__no_qr__introduction" +msgstr "Der QR Code für dieses Token konnte nicht gefunden werden, bitte speicher das Token um einen neuen zu generieren." + msgid "BadgeToken__token__help" msgstr "Mit diesem Token können sich Teilnehmende das Badge holen!" @@ -252,12 +273,6 @@ msgstr "Anzahl der Einlösungen" msgid "BadgeToken__redeemed_count__help" msgstr "Wie viele Teilnehmende haben sich das Badge mit diesem Token geholt?" -msgid "BadgeToken__show_badge_redeem_page" -msgstr "Einlöseseite anzeigen" - -msgid "BadgeToken__no_qr__introduction" -msgstr "Der QR Code für dieses Token konnte nicht gefunden werden, bitte speicher das Token um einen neuen zu generieren." - msgid "Badges" msgstr "Badges" diff --git a/src/backoffice/locale/en/LC_MESSAGES/django.po b/src/backoffice/locale/en/LC_MESSAGES/django.po index 3c10871739105cefd64e89e40e6bcb5a66ae58c0..85291c0e4c340a43cc0d483e82d3f57ed97b1511 100644 --- a/src/backoffice/locale/en/LC_MESSAGES/django.po +++ b/src/backoffice/locale/en/LC_MESSAGES/django.po @@ -240,6 +240,27 @@ msgstr "Redeem Token" msgid "BadgeToken__Badge_back__button %(badge)s" msgstr "Back to %(badge)s" +msgid "BadgeToken__introduction" +msgstr "You can use this token to award participants a badge. You have several options. To display the information in the current design, follow this link:" + +msgid "BadgeToken__redeem__info_link" +msgstr "On the website" + +msgid "BadgeToken__redeem_link_label" +msgstr "Redemption link" + +msgid "BadgeToken__redeem_link__introduction" +msgstr "Participants can use this link to get the badge (also used in the QR code, for example)" + +msgid "BadgeToken__redeem_token_label" +msgstr "Redemption token" + +msgid "BadgeToken__redeem_token__introduction" +msgstr "With this token, users can add the badge to their profile" + +msgid "BadgeToken__no_qr__introduction" +msgstr "The QR code for this token could not be found, please save the token to generate a new one." + msgid "BadgeToken__token__help" msgstr "Participants can use this token to get the badge!" @@ -252,12 +273,6 @@ msgstr "Number of redemptions" msgid "BadgeToken__redeemed_count__help" msgstr "How many participants got the badge with this token?" -msgid "BadgeToken__show_badge_redeem_page" -msgstr "Link to redeem page" - -msgid "BadgeToken__no_qr__introduction" -msgstr "The QR code for this token could not be found, please save the token to generate a new one." - msgid "Badges" msgstr "badges" @@ -2253,3 +2268,6 @@ msgstr "StaticPage and all corresponding revisions deleted." msgid "StaticPageRevision__deleted" msgstr "StaticPageRevision deleted." + +#~ msgid "BadgeToken__show_badge_redeem_page" +#~ msgstr "Link to redeem page" diff --git a/src/backoffice/templates/backoffice/assembly_badge_token.html b/src/backoffice/templates/backoffice/assembly_badge_token.html index 50d16e448e541cc7e08705a14879e9479831703d..2164bf6511c701631a52863d2769c7db90977e70 100644 --- a/src/backoffice/templates/backoffice/assembly_badge_token.html +++ b/src/backoffice/templates/backoffice/assembly_badge_token.html @@ -11,7 +11,7 @@ <div class="row mb-3"> <div class="col-md-12"> <div class="card border-default"> - <div class="card-header {% if new %}bg-success{% else %}bg-default{% endif %}"> + <div class="card-header {% if new %} bg-success {% else %} bg-default {% endif %}"> {% if create_token_form.create %} {% trans "Create" %} {% endif %} @@ -25,7 +25,49 @@ </div> <div class="card-body"> <div class="row"> - <div class="{% if create_token_form.create %}col-md-12{% else %}col-md-10{% endif %}"> + + {% if not create_token_form.create %} + <div class="col-md-12"> + <div class="row mx-1 alert alert-info"> + <div class="col-md-10"> + + <div class="mb-2"> + {% trans "BadgeToken__introduction" %} <a href="{% hub_absolute 'plainui:badge_token' pk=create_token_form.instance.id %}">{% trans "BadgeToken__redeem__info_link" %}</a> + </div> + <div class="input-group mb-1"> + <span class="input-group-text" id="redeem_link_label">{% trans "BadgeToken__redeem_link_label" %}</span> + <input type="text" + class="form-control read-only select-all" + readonly + id="redeem_link" + value="{% hub_absolute 'plainui:manage_badges' redeem_token=create_token_form.instance.token i18n=False %}"> + </div> + <div id="redeem_link_help" class="form-text mb-2">{% trans "BadgeToken__redeem_link__introduction" %}</div> + <div class="input-group mb-1"> + <span class="input-group-text" id="redeem_token_label">{% trans "BadgeToken__redeem_token_label" %}</span> + <input type="text" + class="form-control read-only select-all" + readonly + id="redeem_token" + value="{{ create_token_form.instance.token }}"> + </div> + <div id="redeem_token_help" class="form-text">{% trans "BadgeToken__redeem_token__introduction" %}</div> + </div> + <div class="col-md-2"> + {% if create_token_form.instance.has_qr_code %} + <div class="float-end bg-white"> + <img class="w-100" + src="{{ create_token_form.instance.qr_full.url }}" + alt="Full QR Code to redeem token"> + </div> + {% else %} + <div class="container text-bg-danger">{% trans "BadgeToken__no_qr__introduction" %}</div> + {% endif %} + </div> + </div> + </div> + {% endif %} + <div class="col-md-12"> <form class="form" action="" method="post" enctype="multipart/form-data"> {% csrf_token %} {% if not create_token_form.create %} @@ -63,24 +105,19 @@ </div> </form> </div> - {% if not create_token_form.create %} - <div class="col-md-2 d-flex flex-column"> - {% if create_token_form.instance.has_qr_code %} - <div> - <img class="w-100" - src="{{ create_token_form.instance.qr_full.url }}" - alt="Full QR Code to redeem token"> - <a href="{% hub_absolute 'plainui:badge_token' pk=create_token_form.instance.id %}" - class="btn btn-info">{% trans "BadgeToken__show_badge_redeem_page" %}</a> - </div> - {% else %} - <div class="container text-bg-danger">{% trans "BadgeToken__no_qr__introduction" %}</div> - {% endif %} - </div> - {% endif %} </div> </div> </div> </div> </div> {% endblock content %} + +{% block scripts %} + <scripts nonce="{{ request.csp_nonce }}"> +$(document).ready(function() { +$('.select-all').click(function() { +$(this).select(); +}); +}); + </scripts> +{% endblock scripts %} diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py index 8990771ec281287afde06b9e82804aa0fa103c52..d5c6244df38e965d590a3977e98b727ebbfb4950 100644 --- a/src/core/models/assemblies.py +++ b/src/core/models/assemblies.py @@ -85,7 +85,7 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel): verbose_name = _('Assembly') verbose_name_plural = _('Assemblies') rules_permissions = { - 'view': is_assembly_member, + 'view': is_assembly_member | has_perms('core.view_assembly', require_staff=True), 'add': is_authenticated, 'change': is_assembly_manager | has_perms('core.change_assembly', require_staff=True), 'delete': is_assembly_manager | has_perms('core.delete_assembly', require_staff=True), diff --git a/src/core/models/badges.py b/src/core/models/badges.py index 16b07ac096233797c23ee5949668c2a7c200e6d4..338a50e509dab7443e97d0868c0ec84f0a8349a7 100644 --- a/src/core/models/badges.py +++ b/src/core/models/badges.py @@ -276,7 +276,7 @@ class BadgeToken(models.Model): if not self.has_qr_code: from core.templatetags.hub_absolute import hub_absolute # pylint: disable=import-outside-toplevel - qr_full = segno_make(f"{hub_absolute('plainui:manage_badges')}?redeem_token={self.token}", micro=False) + qr_full = segno_make(hub_absolute('plainui:manage_badges', i18n=False, redeem_token=self.token), micro=False) buffer = io.BytesIO() qr_full.save(buffer, kind='svg', scale=3, dark='#555') buffer_value = buffer.getvalue().decode('utf-8').replace('#555', 'currentColor') diff --git a/src/plainui/urls.py b/src/plainui/urls.py index c6ac7b5d65f7c09d5ed3c91d3cc86511ca4c4d07..69bcc2b6e001f7296bf141bd664100588295003c 100644 --- a/src/plainui/urls.py +++ b/src/plainui/urls.py @@ -29,6 +29,7 @@ urlpatterns = [ path('logout', views.LogoutView.as_view(), name='logout'), path('me', views.ProfileView.as_view(), name='userprofile'), path('me/manage_badges', views.ManageBadgesView.as_view(), name='manage_badges'), + path('me/manage_badges/redeem/<str:redeem_token>', views.ManageBadgesView.as_view(), name='manage_badges'), path('me/manage_badges/settings', views.BadgeSettingsView.as_view(), name='manage_badges__settings'), path('me/manage_badges/<uuid:pk>/accept', views.ManageBadgeView.as_view(mode='accept'), name='manage_badges__accept'), path('me/manage_badges/<uuid:pk>/update', views.ManageBadgeView.as_view(mode='update'), name='manage_badges__update'), diff --git a/src/plainui/views/badges.py b/src/plainui/views/badges.py index dbc254e8b0596f854d073341b9a48adda9ae2fac..4f5ebe98350e76c8c239e2ea7320cf78ab090a84 100644 --- a/src/plainui/views/badges.py +++ b/src/plainui/views/badges.py @@ -24,7 +24,7 @@ from django.views.generic.base import TemplateView from django.views.generic.detail import DetailView from django.views.generic.edit import FormView, UpdateView -from core.models import Assembly, Badge, BadgeToken, UserBadge +from core.models import Badge, BadgeToken, UserBadge from core.models.badges import TokenInvalid from core.models.conference import ConferenceMember from core.views.list_views import FilteredListView @@ -108,6 +108,15 @@ class RedeemTokenDetailView(ConferenceRequiredMixin, DetailView): model = BadgeToken template_name = 'plainui/badges/token_detail.html.j2' context_object_name = 'token' + require_user = True + + def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: + if not BadgeToken.type_is(token := self.get_object()): # pragma: no cover + raise ValueError('Token is not a badge token') + if self.request.user.has_perm('core.view_assembly', token.badge.issuing_assembly): + return super().get(request, *args, **kwargs) + else: + return HttpResponseRedirect(f"{reverse('plainui:manage_badges')}?redeem_token={token.token}") def get_context_data(self, **kwargs: Any) -> dict[str, Any]: # Regenerate the qr code if needed @@ -119,12 +128,6 @@ class RedeemTokenDetailView(ConferenceRequiredMixin, DetailView): 'conf': self.conf, } - def get_queryset(self) -> QuerySet[BadgeToken]: - manageable = Assembly.objects.manageable_by_user(user=self.request.user, conference=self.conf, staff_can_manage=False) - qs = super().get_queryset() - qs = qs.filter(badge__issuing_assembly__in=manageable) - return qs - @method_decorator(ratelimit(group='redeem_badge', key='user', rate=settings.BADGE_RATE_LIMIT), name='dispatch') class RedeemBadgeView(ConferenceRequiredMixin, FormView): @@ -164,7 +167,7 @@ class RedeemBadgeView(ConferenceRequiredMixin, FormView): return context def get(self, request: HttpRequest, *args: str, **kwargs: Any) -> HttpResponse: - token = request.GET.get('redeem_token') + token = request.GET.get('redeem_token', kwargs.get('redeem_token')) try: self.form = RedeemBadgeForm({'token': token, 'purpose': 'redeem_token'}, conference=self.conf) if self.form.is_valid(): @@ -235,7 +238,11 @@ class ManageBadgesView(ConferenceRequiredMixin, TemplateView): return context def dispatch(self, request, *args, **kwargs): - if (hasattr(request, 'GET') and request.GET.get('redeem_token')) or (hasattr(request, 'GET') and request.POST.get('purpose', None) == 'redeem_token'): + if ( + (hasattr(request, 'GET') and request.GET.get('redeem_token')) + or (hasattr(request, 'GET') and request.POST.get('purpose', None) == 'redeem_token') + or kwargs.get('redeem_token') + ): return RedeemBadgeView.as_view(external_context=self.get_context_data())(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)