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> <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> <p class="list-group-item-text">Use this view to see a list of Pos objects, and to see and submit PosReports</p>
</a> </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> </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, SpeakerProposalListView,
SpeakerUpdateView, SpeakerUpdateView,
TicketCheckinView, TicketCheckinView,
TokenCreateView,
TokenDeleteView,
TokenDetailView,
TokenListView,
TokenStatsView,
TokenUpdateView,
VillageOrdersView, VillageOrdersView,
VillageToOrderView, 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 shop.models import Order, OrderProductRelation
from teams.models import Team from teams.models import Team
from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType from tickets.models import DiscountTicket, ShopTicket, SponsorTicket, TicketType
from tokens.models import Token, TokenFind
from utils.models import OutgoingEmail from utils.models import OutgoingEmail
from .forms import ( from .forms import (
@ -2176,3 +2177,92 @@ class PosReportPosCountEndView(PosViewMixin, UpdateView):
super().setup(*args, **kwargs) super().setup(*args, **kwargs)
if self.request.user != self.get_object().pos_responsible: if self.request.user != self.get_object().pos_responsible:
raise PermissionDenied("Only the pos responsible can do this") 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.db import models
from django.urls import reverse
from utils.models import CampRelatedModel from utils.models import CampRelatedModel
@ -21,6 +22,12 @@ class Token(CampRelatedModel):
class Meta: class Meta:
ordering = ["camp"] 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 TokenFind(CampRelatedModel):
class Meta: class Meta:
@ -28,7 +35,9 @@ class TokenFind(CampRelatedModel):
token = models.ForeignKey("tokens.Token", on_delete=models.PROTECT) 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" camp_filter = "token__camp"