From 06f6f97449dd7330a7acede27a48dc13e26942c5 Mon Sep 17 00:00:00 2001 From: Thomas Steen Rasmussen Date: Mon, 17 Aug 2020 15:05:34 +0200 Subject: [PATCH] add some backoffice token views for the gameteam --- src/backoffice/templates/index.html | 12 +++ src/backoffice/templates/token_delete.html | 18 ++++ src/backoffice/templates/token_detail.html | 58 ++++++++++++ src/backoffice/templates/token_form.html | 21 +++++ src/backoffice/templates/token_list.html | 54 +++++++++++ src/backoffice/templates/token_stats.html | 39 ++++++++ src/backoffice/urls.py | 35 ++++++++ src/backoffice/views.py | 90 +++++++++++++++++++ .../0006_set_tokenfind_related_name.py | 25 ++++++ src/tokens/models.py | 11 ++- 10 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 src/backoffice/templates/token_delete.html create mode 100644 src/backoffice/templates/token_detail.html create mode 100644 src/backoffice/templates/token_form.html create mode 100644 src/backoffice/templates/token_list.html create mode 100644 src/backoffice/templates/token_stats.html create mode 100644 src/tokens/migrations/0006_set_tokenfind_related_name.py diff --git a/src/backoffice/templates/index.html b/src/backoffice/templates/index.html index e3ed808d..9070d1f5 100644 --- a/src/backoffice/templates/index.html +++ b/src/backoffice/templates/index.html @@ -172,6 +172,18 @@

Point of Sale

Use this view to see a list of Pos objects, and to see and submit PosReports

+ + {% if perms.camps.gameteam_permission %} +

Secret Tokens

+ +

Tokens

+

Use this view to see a list of Tokens, and to create, update and delete them

+
+ +

Token Stats

+

Use this view to see token find stats

+
+ {% endif %} diff --git a/src/backoffice/templates/token_delete.html b/src/backoffice/templates/token_delete.html new file mode 100644 index 00000000..c1565437 --- /dev/null +++ b/src/backoffice/templates/token_delete.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% load bootstrap3 %} + +{% block content %} +
+
+

Delete Token {{ token.token }}?

+
+
+

This Token has {{ token.tokenfind_set.count }} TokenFinds which will also be deleted.

+
+ {% csrf_token %} + + Cancel +
+
+
+{% endblock content %} diff --git a/src/backoffice/templates/token_detail.html b/src/backoffice/templates/token_detail.html new file mode 100644 index 00000000..564743c3 --- /dev/null +++ b/src/backoffice/templates/token_detail.html @@ -0,0 +1,58 @@ +{% extends 'base.html' %} + +{% block title %} +Token Details | Pos | BackOffice | {{ block.super }} +{% endblock %} + +{% block content %} +
+
+

Token Details | Tokens | BackOffice

+
+
+

+ Update Token + Delete Token + Token List +

+ + + + + + + + + + + + + + + + +
Token{{ token.tokene }}
Category{{ token.category }}

+
Description{{ token.description }}

+
Finds{{ token.tokenfind_set.count }} times

+
+

Token Finds

+ + + + + + + + + {% for tf in token.tokenfind_set.all %} + + + + + + {% endfor %} + +
UsernameTimestamp
{{ tf.user.username }}{{ tf.user.email }}{{ tf.created }}
+
+
+{% endblock %} diff --git a/src/backoffice/templates/token_form.html b/src/backoffice/templates/token_form.html new file mode 100644 index 00000000..91d24127 --- /dev/null +++ b/src/backoffice/templates/token_form.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% load bootstrap3 %} +{% load static %} + +{% block content %} +
+
+

{% if request.resolver_match.url_name == "token_update" %}Update{% else %}Create new{% endif %} Token

+
+
+
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + Cancel + {% endbuttons %} +
+
+
+{% endblock content %} diff --git a/src/backoffice/templates/token_list.html b/src/backoffice/templates/token_list.html new file mode 100644 index 00000000..c51d9a2b --- /dev/null +++ b/src/backoffice/templates/token_list.html @@ -0,0 +1,54 @@ +{% extends 'base.html' %} +{% load bornhack %} + +{% block title %} +Token List | Backoffice | {{ block.super }} +{% endblock %} + +{% block content %} +
+

Token List - BackOffice

+
+

This is a list of all tokens for {{ camp.title }}.

+ {% if not token_list %} +

No tokens found.

+ {% else %} +

+ Backoffice + + + + + + + + + + + + {% for token in token_list %} + + + + + + + + {% endfor %} + +
TokenCategoryDescriptionFindsActions
{{ token.token }}{{ token.category }}{{ token.description }}{{ token.tokenfind_set.count }} + +
+

+ {% endif %} +

+ Create Token + Backoffice +

+
+
+{% endblock content %} diff --git a/src/backoffice/templates/token_stats.html b/src/backoffice/templates/token_stats.html new file mode 100644 index 00000000..e8e8559c --- /dev/null +++ b/src/backoffice/templates/token_stats.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} +{% load bornhack %} + +{% block title %} +Token Stats | Backoffice | {{ block.super }} +{% endblock %} + +{% block content %} +
+

Token Stats - BackOffice

+
+

A list of users and the number of tokens they found

+

+ Backoffice + + + + + + + + + + {% for user in user_list %} + + + + + + {% endfor %} + +
UsernameEmailFinds
{{ user.username }}{{ user.email }}{{ user.token_find_count }}
+

+

+ Backoffice +

+
+
+{% endblock content %} diff --git a/src/backoffice/urls.py b/src/backoffice/urls.py index bed78562..3a547e13 100644 --- a/src/backoffice/urls.py +++ b/src/backoffice/urls.py @@ -91,6 +91,12 @@ from .views import ( SpeakerProposalListView, SpeakerUpdateView, TicketCheckinView, + TokenCreateView, + TokenDeleteView, + TokenDetailView, + TokenListView, + TokenStatsView, + TokenUpdateView, VillageOrdersView, VillageToOrderView, ) @@ -717,4 +723,33 @@ urlpatterns = [ ], ), ), + # tokens + path( + "tokens/", + include( + [ + path("", TokenListView.as_view(), name="token_list",), + path("create/", TokenCreateView.as_view(), name="token_create",), + path("stats/", TokenStatsView.as_view(), name="token_stats",), + path( + "/", + include( + [ + path("", TokenDetailView.as_view(), name="token_detail",), + path( + "update/", + TokenUpdateView.as_view(), + name="token_update", + ), + path( + "delete/", + TokenDeleteView.as_view(), + name="token_delete", + ), + ] + ), + ), + ] + ), + ), ] diff --git a/src/backoffice/views.py b/src/backoffice/views.py index f100f54c..9331c1ce 100644 --- a/src/backoffice/views.py +++ b/src/backoffice/views.py @@ -54,6 +54,7 @@ from program.utils import save_speaker_availability from shop.models import Order, OrderProductRelation from teams.models import Team from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType +from tokens.models import Token, TokenFind from utils.models import OutgoingEmail from .forms import ( @@ -2176,3 +2177,92 @@ class PosReportPosCountEndView(PosViewMixin, UpdateView): super().setup(*args, **kwargs) if self.request.user != self.get_object().pos_responsible: raise PermissionDenied("Only the pos responsible can do this") + + +################################ +# Secret Token views + + +class TokenListView(CampViewMixin, RaisePermissionRequiredMixin, ListView): + """Show a list of secret tokens for this camp""" + + permission_required = ["camps.backoffice_permission", "camps.gameteam_permission"] + model = Token + template_name = "token_list.html" + + +class TokenDetailView(CampViewMixin, RaisePermissionRequiredMixin, DetailView): + """Show details for a token.""" + + permission_required = ["camps.backoffice_permission", "camps.gameteam_permission"] + model = Token + template_name = "token_detail.html" + + +class TokenCreateView(CampViewMixin, RaisePermissionRequiredMixin, CreateView): + """Create a new Token.""" + + permission_required = ["camps.backoffice_permission", "camps.gameteam_permission"] + model = Token + template_name = "token_form.html" + fields = ["token", "category", "description"] + + def form_valid(self, form): + token = form.save(commit=False) + token.camp = self.camp + token.save() + return redirect( + reverse( + "backoffice:token_detail", + kwargs={"camp_slug": self.camp.slug, "pk": token.id}, + ) + ) + + +class TokenUpdateView(CampViewMixin, RaisePermissionRequiredMixin, UpdateView): + """Update a token.""" + + permission_required = ["camps.backoffice_permission", "camps.gameteam_permission"] + model = Token + template_name = "token_form.html" + fields = ["token", "category", "description"] + + +class TokenDeleteView(CampViewMixin, RaisePermissionRequiredMixin, DeleteView): + permission_required = ["camps.backoffice_permission", "camps.gameteam_permission"] + model = Token + template_name = "token_delete.html" + + def delete(self, *args, **kwargs): + self.get_object().tokenfind_set.all().delete() + return super().delete(*args, **kwargs) + + def get_success_url(self): + messages.success( + self.request, "The Token and all related TokenFinds has been deleted" + ) + return reverse("backoffice:token_list", kwargs={"camp_slug": self.camp.slug}) + + +class TokenStatsView(CampViewMixin, RaisePermissionRequiredMixin, ListView): + """Show stats for token finds for this camp""" + + permission_required = ["camps.backoffice_permission", "camps.gameteam_permission"] + model = User + template_name = "token_stats.html" + + def get_queryset(self, **kwargs): + tokenusers = ( + TokenFind.objects.filter(token__camp=self.camp) + .distinct("user") + .values_list("user", flat=True) + ) + return ( + User.objects.filter(id__in=tokenusers) + .annotate( + token_find_count=Count( + "token_finds", filter=Q(token_finds__token__camp=self.camp) + ) + ) + .exclude(token_find_count=0) + ) diff --git a/src/tokens/migrations/0006_set_tokenfind_related_name.py b/src/tokens/migrations/0006_set_tokenfind_related_name.py new file mode 100644 index 00000000..1f3f2823 --- /dev/null +++ b/src/tokens/migrations/0006_set_tokenfind_related_name.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1 on 2020-08-17 12:36 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("tokens", "0005_auto_20190327_2025"), + ] + + operations = [ + migrations.AlterField( + model_name="tokenfind", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="token_finds", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/src/tokens/models.py b/src/tokens/models.py index 00dc77e6..be0c3776 100644 --- a/src/tokens/models.py +++ b/src/tokens/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.urls import reverse from utils.models import CampRelatedModel @@ -21,6 +22,12 @@ class Token(CampRelatedModel): class Meta: ordering = ["camp"] + def get_absolute_url(self): + return reverse( + "backoffice:token_detail", + kwargs={"camp_slug": self.camp.slug, "pk": self.pk}, + ) + class TokenFind(CampRelatedModel): class Meta: @@ -28,7 +35,9 @@ class TokenFind(CampRelatedModel): token = models.ForeignKey("tokens.Token", on_delete=models.PROTECT) - user = models.ForeignKey("auth.User", on_delete=models.PROTECT) + user = models.ForeignKey( + "auth.User", on_delete=models.PROTECT, related_name="token_finds" + ) camp_filter = "token__camp"