]> git.menne-pb.de Git - pinpoint.git/commitdiff
Add test for get_categories view
authorJörn Menne <jmenne@fedora.de>
Sat, 8 Feb 2025 09:02:21 +0000 (10:02 +0100)
committerJörn Menne <jmenne@fedora.de>
Sat, 8 Feb 2025 09:03:14 +0000 (10:03 +0100)
georeport/forms.py [new file with mode: 0644]
georeport/migrations/0002_rename_descrption_report_description_report_latitude_and_more.py [new file with mode: 0644]
georeport/models.py
georeport/static/georeport/recurseCategorySelection.js
georeport/tests.py
georeport/urls.py
georeport/views.py

diff --git a/georeport/forms.py b/georeport/forms.py
new file mode 100644 (file)
index 0000000..5151f63
--- /dev/null
@@ -0,0 +1,16 @@
+# Copyright: (c) 2025, Jörn Menne <jmenne@posteo.de>
+# 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 (file)
index 0000000..6a748dd
--- /dev/null
@@ -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,
+        ),
+    ]
index 123f484ba2655e59ab99e9453f09d10bfdbe85f4..7eac86a5798798c169885f117b8e5b36b32ba313 100644 (file)
@@ -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)
index 1d661b2006e06e3717ad0efa0f8413c39b87216f..fbe7ffc79988843011faa44e7686dc9a4549681c 100644 (file)
@@ -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);
+    })
 }
index 94d3c8f4bdb64d36c6f9d3d4c0d91d403b232cb4..9600f86f3d9e39b3afc2c65243fccea386781137 100644 (file)
@@ -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/<id>/'."""
+        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)
index ae3c2bd40777de5f05dc114b06acdece6d00f38d..fc96246bfd54e341426cfb5f40a8b865bbbc9bf2 100644 (file)
@@ -11,11 +11,11 @@ app_name = "georeport"
 
 urlpatterns = [
     path("", views.index, name="index"),
-    #  path("<int:id>", views.details, name="detail"),
-    #  path("create", views.create, name="create"),
     path("category/<int:id>", views.category_detail_view, name="category"),
-    path("services/<int:id>", views.category_detail_view, name="servcice"),
     path("category/<int:id>/children", views.get_categories, name="subcategories"),
-    path("services/", views.get_categories),
+    path("services/<int:id>", views.category_detail_view, name="servcice"),
+    path("services/", views.get_categories, name="category-list"),
+    # path("<int:id>", views.details, name="detail"),
+    #  path("create", views.create, name="create"),
     #  path("<str:b64nonce>/<str:b64ct>", views.finish_link, name="finish"),
 ]
index 7c2f731f2bf1766a63a0b835e872ffa82106743c..6e253bb2d71e8941460dbebf2564e49abf1bea94 100644 (file)
@@ -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