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>
|
<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>
|
||||||
|
|
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,
|
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",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
|
|
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.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"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue