From: Jörn Menne Date: Sat, 8 Feb 2025 09:02:21 +0000 (+0100) Subject: Add test for get_categories view X-Git-Url: https://git.menne-pb.de/?a=commitdiff_plain;h=5e2e42ae3e45ca1f90a50bc695bfe59ad91841b3;p=pinpoint.git Add test for get_categories view --- diff --git a/georeport/forms.py b/georeport/forms.py new file mode 100644 index 0000000..5151f63 --- /dev/null +++ b/georeport/forms.py @@ -0,0 +1,16 @@ +# Copyright: (c) 2025, Jörn Menne +# GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-3.0.md) + +from django.forms import ModelForm +from .models import Report + + +class ReportForm(ModelForm): + """ + Small class to map a dictionary to the report model. + Thic class extends the django-ModelForm + """ + + class Meta: + model = Report + fields = ["title", "description", "email", "category"] diff --git a/georeport/migrations/0002_rename_descrption_report_description_report_latitude_and_more.py b/georeport/migrations/0002_rename_descrption_report_description_report_latitude_and_more.py new file mode 100644 index 0000000..6a748dd --- /dev/null +++ b/georeport/migrations/0002_rename_descrption_report_description_report_latitude_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.5 on 2025-02-08 08:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('georeport', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='report', + old_name='descrption', + new_name='description', + ), + migrations.AddField( + model_name='report', + name='latitude', + field=models.DecimalField(decimal_places=6, default=0, max_digits=8), + preserve_default=False, + ), + migrations.AddField( + model_name='report', + name='longitude', + field=models.DecimalField(decimal_places=6, default=0, max_digits=9), + preserve_default=False, + ), + ] diff --git a/georeport/models.py b/georeport/models.py index 123f484..7eac86a 100644 --- a/georeport/models.py +++ b/georeport/models.py @@ -9,6 +9,8 @@ from django.db import models from django.contrib.auth.models import Group, User from typing import override +from django.forms import DecimalField + # Create your models here. @@ -62,7 +64,7 @@ class Report(models.Model): Category, on_delete=models.RESTRICT, related_name="reports" ) title = models.CharField(max_length=100, unique=True) - descrption = models.TextField(blank=True, null=True) + description = models.TextField(blank=True, null=True) email = models.EmailField() # TODO: Images @@ -75,6 +77,12 @@ class Report(models.Model): """ published = models.BooleanField(default=False) # type: ignore Correct type can not be dtermined + # Location + # NOTE: Latitude is between -90 and 90°, while Longitude is between -180 and 180° + # Therefore the latitude field is slightly smaller + longitude = models.DecimalField(max_digits=9, decimal_places=6) + latitude = models.DecimalField(max_digits=8, decimal_places=6) + @override def __str__(self) -> str: return str(self.title) diff --git a/georeport/static/georeport/recurseCategorySelection.js b/georeport/static/georeport/recurseCategorySelection.js index 1d661b2..fbe7ffc 100644 --- a/georeport/static/georeport/recurseCategorySelection.js +++ b/georeport/static/georeport/recurseCategorySelection.js @@ -14,89 +14,89 @@ var maxlevel = 0; function getsubcats(element) { - // Set the current level to know which selections have to be removed (if any) - const id = element.id; - var level; - if (id == "category") - level = 0; - else - level = id; - console.log(level); + // Set the current level to know which selections have to be removed (if any) + const id = element.id; + var level; + if (id == "category") + level = 0; + else + level = id; + console.log(level); - // Get surrunding elements - const form = document.getElementById("form"); - const submit = document.getElementById("submit"); + // Get surrunding elements + const form = document.getElementById("form"); + const submit = document.getElementById("submit"); - //create a url to fetch the children - let url = "category/${element.value}/children"; - console.log(url); - fetch(url) - // Check if the response is correct - .then(response => { - if (!response.ok) - throw new Error("HTTP Error: Status: ${response.status}"); - return response.json(); - }) - //Handle the json-Data - .then(data => { - console.log(data); - // NOTE: Level has to be increased here, since if it would be later increased, it would be handled as a string - // while removing the higher levels + //create a url to fetch the children + let url = "category/${element.value}/children"; + console.log(url); + fetch(url) + // Check if the response is correct + .then(response => { + if (!response.ok) + throw new Error("HTTP Error: Status: ${response.status}"); + return response.json(); + }) + //Handle the json-Data + .then(data => { + console.log(data); + // NOTE: Level has to be increased here, since if it would be later increased, it would be handled as a string + // while removing the higher levels - g - level++; - let subcats = data["subcategories"]; - //Remove submit temporarly to set the correct position - form.removeChild(submit); + g + level++; + let subcats = data["categories"]; + //Remove submit temporarly to set the correct position + form.removeChild(submit); - //Remove all selects with a higher level than element - if (maxlevel >= level) { - for (let i = level; i <= maxlevel; i++) { - oldselect = document.getElementById(i); - oldselect.remove(); - } - } - let oldselect = document.getElementById(level); + //Remove all selects with a higher level than element + if (maxlevel >= level) { + for (let i = level; i <= maxlevel; i++) { + oldselect = document.getElementById(i); + oldselect.remove(); + } + } + let oldselect = document.getElementById(level); - if (subcats.length == 0) { - // set element as the lowest level - element.name = "category"; - maxlevel = level - 1; - } - else { + if (subcats.length == 0) { + // set element as the lowest level + element.name = "category"; + maxlevel = level - 1; + } + else { - //Create a new selection element as lowest level - let select = document.createElement("select"); - select.id = level; - select.name = select.id; - select.value = ""; - select.innerHTML = "Choose a subcategory"; - select.onchange = function () { - getsubcats(this); - } - select.name = "category"; + //Create a new selection element as lowest level + let select = document.createElement("select"); + select.id = level; + select.name = select.id; + select.value = ""; + select.innerHTML = "Choose a subcategory"; + select.onchange = function () { + getsubcats(this); + } + select.name = "category"; - // Create the new options - var option = document.createElement("option"); - option.value = ""; - option.innerHTML = "Subcategory"; - option.disabled = true; - option.selected = true; - select.appendChild(option); + // Create the new options + var option = document.createElement("option"); + option.value = ""; + option.innerHTML = "Subcategory"; + option.disabled = true; + option.selected = true; + select.appendChild(option); - for (var cat of subcats) { - option = document.createElement("option") - option.value = cat.id; - option.innerHTML = cat.name; - select.appendChild(option); - } + for (var cat of subcats) { + option = document.createElement("option") + option.value = cat.id; + option.innerHTML = cat.name; + select.appendChild(option); + } - element.name = "root"; - form.replaceChild(select, oldselect); - maxlevel = level; - } - //Reappend submit - form.appendChild(submit); - }) + element.name = "root"; + form.replaceChild(select, oldselect); + maxlevel = level; + } + //Reappend submit + form.appendChild(submit); + }) } diff --git a/georeport/tests.py b/georeport/tests.py index 94d3c8f..9600f86 100644 --- a/georeport/tests.py +++ b/georeport/tests.py @@ -2,6 +2,7 @@ # GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-3.0.md) from django.test import TestCase +from django.urls import reverse from .models import Category, Report @@ -13,8 +14,94 @@ class ReportTestCase(TestCase): title="Test", email="test@test.de", category=Category.objects.first(), # type:ignore Attribute object is unknown + latitude=0, + longitude=0, ) def test_unpulished_as_default(self): report = Report.objects.get(title="Test") # type:ignore Attribute object is unknown self.assertEqual(report.published, False) + + +# TODO: Test latlng + + +# TODO: Test get_categories + + +class CategoryViewTests(TestCase): + @classmethod + def setUpTestData(cls): + """ + Creates a test dataset: + - Root categories (no parent) + - Nested categories (children of other categories) + """ + cls.root1 = Category.objects.create(name="Root 1") # type: ignore Attribute object unknown + cls.root2 = Category.objects.create(name="Root 2") # type: ignore Attribute object unknown + + cls.child1 = Category.objects.create(name="Child 1", parent=cls.root1) # type: ignore Attribute object unknown + cls.child2 = Category.objects.create(name="Child 2", parent=cls.root1) # type: ignore Attribute object unknown + cls.child3 = Category.objects.create(name="Child 3", parent=cls.root2) # type: ignore Attribute object unknown + + cls.subchild1 = Category.objects.create(name="SubChild 1", parent=cls.child1) # type: ignore Attribute object unknown + + def test_get_all_categories(self): + """ + Test if all categories are within the response + """ + response = self.client.get(reverse("georeport:category-list")) + self.assertEqual(response.status_code, 200) # type: ignore Attribute status_code unknown + data = response.json() # type: ignore Attribute json unknown + expected_ids = { + self.root1.id, + self.root2.id, + self.child1.id, + self.child2.id, + self.child3.id, + self.subchild1.id, + } + response_ids = {cat["id"] for cat in data["categories"]} + self.assertEqual(response_ids, expected_ids) + + def test_get_children_of_valid_category(self): + """Test that only direct children of a given category are returned when accessing 'subcategories//'.""" + response = self.client.get( + reverse("georeport:subcategories", args=[self.root1.id]) + ) + self.assertEqual(response.status_code, 200) # type: ignore Attribute status_code unknown + + data = response.json() # type: ignore Attribute json unknown + expected_ids = {self.child1.id, self.child2.id} + response_ids = {category["id"] for category in data["categories"]} + self.assertSetEqual(response_ids, expected_ids) + + def test_get_children_of_category_without_children(self): + """Test that requesting children of a category with no children returns an empty list.""" + response = self.client.get( + reverse("georeport:subcategories", args=[self.child2.id]) + ) + self.assertEqual(response.status_code, 200) # type: ignore Attribute status_code unknown + + data = response.json() # type: ignore Attribute json unknown + self.assertEqual(data["categories"], []) + + def test_get_children_of_non_existent_category(self): + """Test that requesting children of a non-existent category returns an empty list.""" + response = self.client.get(reverse("georeport:subcategories", args=[99999])) + self.assertEqual(response.status_code, 200) # type: ignore Attribute status_code unknown + + data = response.json() # type: ignore Attribute json unknown + self.assertEqual(data["categories"], []) + + def test_response_format(self): + """Test that response format is correct.""" + response = self.client.get(reverse("georeport:category-list")) + self.assertEqual(response.status_code, 200) # type: ignore Attribute status_code unknown + + data = response.json() # type: ignore Attribute json unknown + self.assertIn("categories", data) + if data["categories"]: # If categories exist, check their structure + category = data["categories"][0] + self.assertIn("id", category) + self.assertIn("name", category) diff --git a/georeport/urls.py b/georeport/urls.py index ae3c2bd..fc96246 100644 --- a/georeport/urls.py +++ b/georeport/urls.py @@ -11,11 +11,11 @@ app_name = "georeport" urlpatterns = [ path("", views.index, name="index"), - # path("", views.details, name="detail"), - # path("create", views.create, name="create"), path("category/", views.category_detail_view, name="category"), - path("services/", views.category_detail_view, name="servcice"), path("category//children", views.get_categories, name="subcategories"), - path("services/", views.get_categories), + path("services/", views.category_detail_view, name="servcice"), + path("services/", views.get_categories, name="category-list"), + # path("", views.details, name="detail"), + # path("create", views.create, name="create"), # path("/", views.finish_link, name="finish"), ] diff --git a/georeport/views.py b/georeport/views.py index 7c2f731..6e253bb 100644 --- a/georeport/views.py +++ b/georeport/views.py @@ -10,10 +10,12 @@ A view takes a request and creates a respond for the request. from django.core.exceptions import PermissionDenied from django.http import HttpResponse, HttpResponseForbidden, JsonResponse from django.shortcuts import get_object_or_404, render -from django.views.decorators.http import require_GET, require_safe +from django.views.decorators.http import require_GET, require_safe, require_http_methods from .models import Category, Report +from .forms import ReportForm + @require_safe def index(request) -> HttpResponse: @@ -37,7 +39,7 @@ def index(request) -> HttpResponse: def get_categories(request, id=None) -> JsonResponse: """ Creates a jsonResponse containing the available categories. - If an id was given, only the subcategories of the category with the given id are returned. + If an id was given, only direct subcategories of the category with the given id are returned. Arguments: request: HttpRequest @@ -54,13 +56,14 @@ def get_categories(request, id=None) -> JsonResponse: else: cats = Category.objects.filter(parent__id=id) # type: ignore Attribute object us unknown data = [{"id": cat.id, "name": cat.name} for cat in cats] - data = {"subcategories": data} + data = {"categories": data} return JsonResponse(data) # TODO: Category-Detail +@require_safe def category_detail_view(request, id) -> HttpResponse: """ Function to handle requests to see information about a single category identified by id @@ -96,6 +99,30 @@ def category_detail_view(request, id) -> HttpResponse: # TODO: Report-List # TODO: Create-Report + +@require_http_methods(["GET", "POST"]) +def create_report_view(request): + if request.method == "POST": + post = request.POST + + # create custom dictionary for Report-Form + report = {} + report["title"] = post["title"] + report["description"] = post["description"] + report["category"] = post["category"] + report["email"] = post["email"] + + reportform = ReportForm(report) + + # TODO: Location + + return render( + request, + "georeport/create.html", + context={"categories": Category.objects.all()}, # type: ignore Attribute objects unknown + ) + + # TODO: Detail-View Report # TODO: Finish Link