add some backoffice token views for the gameteam

This commit is contained in:
Thomas Steen Rasmussen 2020-08-17 15:05:34 +02:00
parent 5b5a400027
commit 06f6f97449
10 changed files with 362 additions and 1 deletions

View file

@ -172,6 +172,18 @@
<h4 class="list-group-item-heading">Point of Sale</h4>
<p class="list-group-item-text">Use this view to see a list of Pos objects, and to see and submit PosReports</p>
</a>
{% if perms.camps.gameteam_permission %}
<h3>Secret Tokens</h3>
<a href="{% url 'backoffice:token_list' camp_slug=camp.slug %}" class="list-group-item">
<h4 class="list-group-item-heading">Tokens</h4>
<p class="list-group-item-text">Use this view to see a list of Tokens, and to create, update and delete them</p>
</a>
<a href="{% url 'backoffice:token_stats' camp_slug=camp.slug %}" class="list-group-item">
<h4 class="list-group-item-heading">Token Stats</h4>
<p class="list-group-item-text">Use this view to see token find stats</p>
</a>
{% endif %}
</div>
</div>
</div>

View file

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Delete Token {{ token.token }}?</h2>
</div>
<div class="panel-body">
<p class="lead">This Token has <b>{{ token.tokenfind_set.count }}</b> TokenFinds which will also be deleted.</p>
<form method="POST">
{% csrf_token %}
<button type="submit" name="Delete" class="btn btn-danger"><i class="fas fa-times"></i> Yes, Delete it</button>
<a href="{% url 'backoffice:token_detail' camp_slug=camp.slug pk=token.pk %}" class="btn btn-default"><i class="fas fa-undo"></i> Cancel</a>
</form>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,58 @@
{% extends 'base.html' %}
{% block title %}
Token Details | Pos | BackOffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Token Details | Tokens | BackOffice</h3>
</div>
<div class="panel-body">
<p>
<a href="{% url 'backoffice:token_update' camp_slug=camp.slug pk=token.pk %}" class="btn btn-primary"><i class="fas fa-edit"></i> Update Token</a>
<a href="{% url 'backoffice:token_delete' camp_slug=camp.slug pk=token.pk %}" class="btn btn-danger"><i class="fas fa-times"></i> Delete Token</a>
<a href="{% url 'backoffice:token_list' camp_slug=camp.slug %}" class="btn btn-default"><i class="fas fa-undo"></i> Token List</a>
</p>
<table class="table">
<tbody>
<tr>
<th>Token</th>
<td>{{ token.tokene }}</td>
</tr>
<tr>
<th>Category</th>
<td>{{ token.category }}</p>
</tr>
<tr>
<th>Description</th>
<td>{{ token.description }}</p>
</tr>
<tr>
<th>Finds</th>
<td>{{ token.tokenfind_set.count }} times</p>
</tr>
</tbody>
</table>
<h3>Token Finds</h3>
<table class="table table-striped{% if not nodatatable %} datatable{% endif %}">
<thead>
<tr>
<th>Username</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
{% for tf in token.tokenfind_set.all %}
<tr>
<td>{{ tf.user.username }}</td>
<td>{{ tf.user.email }}</td>
<td>{{ tf.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% load static %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{% if request.resolver_match.url_name == "token_update" %}Update{% else %}Create new{% endif %} Token</h3>
</div>
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-success"><i class="fas fa-check"></i> Save</button>
<a href="{% url 'backoffice:pos_list' camp_slug=camp.slug %}" class="btn btn-default"><i class="fas fa-undo"></i> Cancel</a>
{% endbuttons %}
</form>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% load bornhack %}
{% block title %}
Token List | Backoffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">Token List - BackOffice</h3></div>
<div class="panel-body">
<p>This is a list of all tokens for {{ camp.title }}.</p>
{% if not token_list %}
<p class="lead">No tokens found.</p>
{% else %}
<p>
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
<table class="table table-striped{% if not nodatatable %} datatable{% endif %}">
<thead>
<tr>
<th>Token</th>
<th>Category</th>
<th>Description</th>
<th>Finds</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for token in token_list %}
<tr>
<td><a href="{% url 'backoffice:token_detail' camp_slug=camp.slug pk=token.pk %}">{{ token.token }}</a></td>
<td>{{ token.category }}</td>
<td>{{ token.description }}</td>
<td>{{ token.tokenfind_set.count }}</td>
<td>
<div class="btn-group-vertical">
<a href="{% url 'backoffice:token_detail' camp_slug=camp.slug pk=token.pk %}" class="btn btn-primary"><i class="fas fa-search"></i> Details</a>
<a href="{% url 'backoffice:token_update' camp_slug=camp.slug pk=token.pk %}" class="btn btn-primary"><i class="fas fa-edit"></i> Update</a>
<a href="{% url 'backoffice:token_delete' camp_slug=camp.slug pk=token.pk %}" class="btn btn-danger"><i class="fas fa-times"></i> Delete</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
{% endif %}
<p>
<a class="btn btn-success" href="{% url 'backoffice:token_create' camp_slug=camp.slug %}"><i class="fas fa-plus"></i> Create Token</a>
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
</p>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% load bornhack %}
{% block title %}
Token Stats | Backoffice | {{ block.super }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title">Token Stats - BackOffice</h3></div>
<div class="panel-body">
<p>A list of users and the number of tokens they found</p>
<p>
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
<table class="table table-striped{% if not nodatatable %} datatable{% endif %}">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Finds</th>
</tr>
</thead>
<tbody>
{% for user in user_list %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.token_find_count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
<p>
<a class="btn btn-default" href="{% url 'backoffice:index' camp_slug=camp.slug %}"><i class="fas fa-undo"></i> Backoffice</a>
</p>
</div>
</div>
{% endblock content %}

View file

@ -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(
"<int:pk>/",
include(
[
path("", TokenDetailView.as_view(), name="token_detail",),
path(
"update/",
TokenUpdateView.as_view(),
name="token_update",
),
path(
"delete/",
TokenDeleteView.as_view(),
name="token_delete",
),
]
),
),
]
),
),
]

View file

@ -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)
)

View file

@ -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,
),
),
]

View file

@ -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"