add some backoffice token views for the gameteam
This commit is contained in:
parent
5b5a400027
commit
06f6f97449
|
@ -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>
|
||||
|
|
18
src/backoffice/templates/token_delete.html
Normal file
18
src/backoffice/templates/token_delete.html
Normal 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 %}
|
58
src/backoffice/templates/token_detail.html
Normal file
58
src/backoffice/templates/token_detail.html
Normal 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 %}
|
21
src/backoffice/templates/token_form.html
Normal file
21
src/backoffice/templates/token_form.html
Normal 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 %}
|
54
src/backoffice/templates/token_list.html
Normal file
54
src/backoffice/templates/token_list.html
Normal 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 %}
|
39
src/backoffice/templates/token_stats.html
Normal file
39
src/backoffice/templates/token_stats.html
Normal 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 %}
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
25
src/tokens/migrations/0006_set_tokenfind_related_name.py
Normal file
25
src/tokens/migrations/0006_set_tokenfind_related_name.py
Normal 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,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in a new issue