Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/backoffice/templates/token_category_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ <h3 class="card-title">Token Category Details | BackOffice</h3>
</tr>
<tr>
<th>Used by:</th>
<td>{{ object.token_set.count }}</td>
<td>{{ category_tokens | length }}</td>
</tr>
<tr>
<th>Created:</th>
Expand All @@ -50,7 +50,7 @@ <h3>Related tokens</h3>
</tr>
</thead>
<tbody>
{% for token in object.token_set.all %}
{% for token in category_tokens %}
<tr>
<td><a href="{% url 'backoffice:token_detail' camp_slug=token.camp.slug pk=token.pk %}">{{ token.token }}</a></td>
<td>{{ token.camp }}</td>
Expand Down
2 changes: 1 addition & 1 deletion src/backoffice/templates/token_category_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ <h3 class="card-title">Token Category List - BackOffice</h3>
<tr>
<td><a href="{% url 'backoffice:token_category_detail' camp_slug=camp.slug pk=category.pk %}">{{ category.name }}</a></td>
<td>{{ category.description }}</td>
<td class="text-start">{{ category.token_set.count }}</td>
<td class="text-start">{{ category.token_count }}</td>
<td>
<a href="{% url 'backoffice:token_create' camp_slug=camp.slug %}" class="me-1"><i class="fas fa-add text-success"></i></a>
<a href="{% url 'backoffice:token_category_detail' camp_slug=camp.slug pk=category.pk %}" class="me-1"><i class="fas fa-search text-info"></i></a>
Expand Down
99 changes: 99 additions & 0 deletions src/backoffice/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import pytest
import uuid

from tokens.models import TokenCategory
from tokens.models import TokenFind
from tokens.models import Token


@pytest.fixture
def token_category_factory(db):
"""Fixture for returning a factory function."""

def create_token_category(persist=False, **kwargs) -> TokenCategory:
"""Create a TokenCategory instance. Saves to DB if persist=True."""
defaults = {
"name": "token-category",
"description": "token category description",
}

defaults.update(**kwargs)

category = TokenCategory(**defaults)

if persist:
category.save()

return category

return create_token_category


@pytest.fixture
def token_category(token_category_factory) -> TokenCategory:
"""Fixture for a single TokenCategory"""
return token_category_factory(persist=True)


@pytest.fixture
def token_factory(db, camp, token_category):
"""Fixture for returning a factory function."""

def create_token(persist=False, **kwargs) -> Token:
"""Create a Token instance. Saves to DB if persist=True."""
defaults = {
"category": token_category,
"camp": camp,
"token": str(uuid.uuid4()).replace('-', ''),
"hint": "token-hint",
"description": "token description",
"active": True,
"valid_when": None,
}

defaults.update(**kwargs)

token = Token(**defaults)

if persist:
token.save()

return token

return create_token


@pytest.fixture
def token(token_factory) -> Token:
"""Fixture for returning a single active token"""
return token_factory(persist=True)


@pytest.fixture
def token_find_factory(db, token, users):
"""Fixture for returning a factory function."""

def create_token_find(persist=False, **kwargs) -> TokenFind:
"""Create a TokenFind instance. Saves to DB if persist=True."""
defaults = {
"token": token,
"user": users[0],
}

defaults.update(**kwargs)

token_find = TokenFind(**defaults)

if persist:
token_find.save()

return token_find

return create_token_find


@pytest.fixture
def token_find(token_find_factory) -> TokenFind:
"""Fixture for returning a single token find."""
return token_find_factory(persist=True)

100 changes: 100 additions & 0 deletions src/backoffice/tests/test_game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import pytest

from django.urls import reverse

from tokens.models import TokenCategory


class TestTokenCategoryListView:
"""Class for grouping TokenCategoryListView tests."""

@pytest.fixture
def url(self, camp) -> str:
"""Reverse URL for view."""
kwargs = {"camp_slug": camp.slug}
return reverse("backoffice:token_category_list", kwargs=kwargs)

@pytest.fixture
def mixed_categories(self, token_category_factory, token_factory) -> list:
"""Fixture for categories with and without related tokens."""
categories = []
for i in range(5):
category = token_category_factory(persist=True, name=f"category{i}")

if i % 2 == 0:
token_factory(persist=True, category=category)

categories.append(category)

return categories

@pytest.mark.usefixtures("mixed_categories")
def test_all_categories_exist_in_queryset(self, admin_client):
"""Test if all categories with and without Tokens exist in queryset."""
expected = TokenCategory.objects.all()

response = admin_client.get(admin_client.url)
result = response.context.get("object_list")

assert result.count() == expected.count()

def test_annotate_token_count_for_current_camp(
self,
admin_client,
token_category,
token_factory,
test_camps
):
"""Test the object list has annotated token count for current camp."""
expected = 0

response = admin_client.get(admin_client.url)
result = response.context.get("object_list")

assert result.get(pk=token_category.pk).token_count == expected

token_count = 6
expected = token_count / len(test_camps)
for i in range(token_count):
token_factory(persist=True, camp=test_camps[i % 3])

response = admin_client.get(admin_client.url)
result = response.context.get("object_list")

assert result.get(pk=token_category.pk).token_count == expected


class TestTokenCategoryDetailView:
"""Class for grouping TokenCategoryDetailView tests."""

@pytest.fixture
def url(self, camp, token_category) -> str:
"""Reverse URL for view."""
kwargs = {"camp_slug": camp.slug, "pk": token_category.pk}
return reverse("backoffice:token_category_detail", kwargs=kwargs)

def test_add_category_tokens_for_current_camp_to_context(
self,
admin_client,
token_category,
token_category_factory,
test_camps,
token_factory
):
"""Test only tokens for this category and camp is added to context."""
category2 = token_category_factory(persist=True, name="test")

expected = []
for camp in list(test_camps):
for _ in range(3):
if camp == test_camps.current:
expected.append(
token_factory(persist=True, camp=camp, category=token_category)
)
token_factory(persist=True, camp=camp, category=category2)

response = admin_client.get(admin_client.url)
result = response.context.get("category_tokens")

assert list(result) == expected

29 changes: 29 additions & 0 deletions src/backoffice/views/game.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import logging
import uuid

from django.contrib import messages
from django.contrib.auth.models import User
Expand Down Expand Up @@ -53,6 +54,16 @@ class TokenCreateView(CampViewMixin, RaisePermissionRequiredMixin, CreateView):
template_name = "token_form.html"
fields = ["token", "category", "hint", "description", "active", "valid_when"]

def get_initial(self) -> dict:
"""Update initial form data with a unique token."""
initial = super().get_initial()
DEFAULT_LENGTH = 18
initial.update({
"token": str(uuid.uuid4()).replace("-", "")[:DEFAULT_LENGTH]
})
return initial


def form_valid(self, form):
token = form.save(commit=False)
token.camp = self.camp
Expand Down Expand Up @@ -130,6 +141,13 @@ class TokenCategoryListView(CampViewMixin, RaisePermissionRequiredMixin, ListVie
model = TokenCategory
template_name = "token_category_list.html"

def get_queryset(self):
"""
Get all Categories and annotate count of tokens for the current camp.
"""
camp_tokens = Count("token", filter=Q(token__camp=self.request.camp))
return TokenCategory.objects.annotate(token_count=camp_tokens)


class TokenCategoryCreateView(CampViewMixin, RaisePermissionRequiredMixin, CreateView):
"""Create a new token category."""
Expand Down Expand Up @@ -158,6 +176,17 @@ class TokenCategoryDetailView(CampViewMixin, RaisePermissionRequiredMixin, Detai
model = TokenCategory
template_name = "token_category_detail.html"

def get_context_data(self, **kwargs):
"""Add all category tokens for the current camp to context."""
context = super().get_context_data(**kwargs)
context.update({
"category_tokens": Token.objects.filter(
category=self.object,
camp=self.request.camp
)
})
return context


class TokenCategoryDeleteView(CampViewMixin, RaisePermissionRequiredMixin, DeleteView):
"""Delete a token category"""
Expand Down
Loading
Loading