]> git.menne-pb.de Git - pinpoint.git/commitdiff
Readd getCategories views and adjustments for open311.
authorJörn Menne <jmenne@fedora.de>
Fri, 7 Feb 2025 12:52:24 +0000 (13:52 +0100)
committerJörn Menne <jmenne@fedora.de>
Fri, 7 Feb 2025 14:05:30 +0000 (15:05 +0100)
15 files changed:
georeport/admin.py
georeport/static/georeport/addMarker.js [new file with mode: 0644]
georeport/static/georeport/mapsetup.js
georeport/static/georeport/recurseCategorySelection.js [new file with mode: 0644]
georeport/static/georeport/retreiveCoordinates.js [new file with mode: 0644]
georeport/static/georeport/style.css
georeport/templates/georeport/base.html
georeport/templates/georeport/category.html [new file with mode: 0644]
georeport/templates/georeport/create.html [new file with mode: 0644]
georeport/templates/georeport/detail.html [new file with mode: 0644]
georeport/templates/georeport/index.html [new file with mode: 0644]
georeport/urls.py
georeport/views.py
pinpoint_report/settings.py
pinpoint_report/urls.py

index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..f22a8a135d136d0b4ef74ba8f7871509c11ffcdf 100644 (file)
@@ -1,3 +1,41 @@
+# 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.contrib import admin
+from typing import override
 
-# Register your models here.
+from georeport.models import Category, Report
+
+
+# TODO: CategoryAdmin
+
+
+class CategoryInline(admin.TabularInline):
+    model = Category
+    extra = 0
+    can_delete = False
+    show_change_link = True
+
+    @override
+    def has_change_permission(self, request, obj=None):
+        return False
+
+
+@admin.register(Category)
+class CategoryAdmin(admin.ModelAdmin):
+    """
+    Class extending model-Admin to register the model Category on
+    the admin site, such that the model can be edited on there.
+    """
+
+    exclude = ["user", "groups"]
+    inlines = [CategoryInline]
+    search_fields = ["name"]
+    # TODO: Prevent circles while creating groups
+
+
+# TODO: ReportAdmin
+@admin.register(Report)
+class ReportAdmin(admin.ModelAdmin):
+    exclude = [
+        "_oldstate",
+    ]
diff --git a/georeport/static/georeport/addMarker.js b/georeport/static/georeport/addMarker.js
new file mode 100644 (file)
index 0000000..66c6f74
--- /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)
+*/
+
+/*
+ * A simple script, which extracts the latitude and longitude value from http
+ * and adds a marker at the correspoing location on the map.
+*/
+
+var marker = L.marker()
+
+var lat = document.getElementById("p-lat").dataset.lat;
+var lng = documetn.getElementById("p-lng").dataset.lng;
+
+marker.setLatLng([lat, lng]).addTo(map);
index de15e519af3ff82fd43ad9a22d8baf5f05f8c182..1f9be5c77a21c8fcb1f4f421cf3dac2966828342 100644 (file)
@@ -3,6 +3,8 @@
  * GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-3.0.md)
 */
 // Center the map-view on Paderborn and use openstreetmap as a mapservice
+
+// Define the map and set the tilelayer
 var map = L.map("map").setView([51.7173, 8.753557], 15);
 L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
     maxZoom: 19,
diff --git a/georeport/static/georeport/recurseCategorySelection.js b/georeport/static/georeport/recurseCategorySelection.js
new file mode 100644 (file)
index 0000000..1d661b2
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * 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)
+*/
+
+/*
+ * Function which adds a new category-selection field, if the current category has at least one subcategory.
+ * If this is not the case, and the former selection had a subcategory, the selection fields for 
+ * subcategories are removed.
+*/
+
+// Global variable to check the depth of the selections
+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);
+
+    // 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
+
+            g
+            level++;
+            let subcats = data["subcategories"];
+            //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);
+
+
+            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 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);
+                }
+
+                element.name = "root";
+                form.replaceChild(select, oldselect);
+                maxlevel = level;
+            }
+            //Reappend submit
+            form.appendChild(submit);
+        })
+}
diff --git a/georeport/static/georeport/retreiveCoordinates.js b/georeport/static/georeport/retreiveCoordinates.js
new file mode 100644 (file)
index 0000000..acb4732
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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)
+*/
+/*
+ * A small script, which extracts the coordinates given by leaflet
+ * and inserts them into the correct fields. It also works the other way.
+ */
+
+// Specify all needed elements
+var lat_element = document.getElementById("latitude");
+var lng_element = document.getElementById("longitude");
+let marker = L.marker();
+
+
+// Add change listener to the input-elements
+lat_element.addEventListener("change", () => {
+    marker.setLatLng([lat_element.value, lng_element.value])
+        .addTo(map);
+});
+lng_element.addEventListener("change", () => {
+    marker.setLatLng([lat_element.value, lng_element.value])
+        .addTo(map);
+});
+
+
+/*
+ * Read event-data if clicked on the map to get the geocoordinates.
+ * The values are then capped to 6 decimals to get a precision of ~10cm.
+ * Which is enoug for this usecase.
+ * The precirsion is accorcding to https://en.wikipedia.org/wiki/Decimal_degrees
+ */
+function onMapClick(e, decimal_precision = 6) {
+    marker.setLatLng(e.latlng).addTo(map);
+
+    lat_element.value = e.latlng.lat.toFixed(decimal_precision);
+    lng_element.value = e.latlng.lng.toFixed(decimal_precision);
+
+}
index ecad1baaa023661d916bf5163d5e2aa15c6429a7..ebca29b762d2f3f75eaa74a56fd2a4609043c21e 100644 (file)
@@ -1,3 +1,58 @@
-#map {
-    height: 500px;
+body {
+    background-color: whitesmoke;
+}
+
+h1 {
+    text-align: center;
+}
+
+#map{
+    height: 400px;
+    width: 75%;
+    border: 3px solid darkgray;
+    margin: auto;
+    border-radius: 25px;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+    padding: 15px;
+
+}
+
+
+.container{
+    width: 75%;
+    margin: auto;
+}
+
+.content{
+    display:flex;
+    flex-wrap: wrap;
+    flex-direction:  row;
+    justify-content: border-box;
+    align-items: center;
+}
+
+.list{
+    flex: 1;
+    border-radius: 25px;
+    margin: 10px;
+    padding: 15px;
+    background-color: white;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+}
+
+.detail{
+    flex:1;
+    max-width: 75%;
+    border-radius: 25px;
+    margin: auto;
+    margin-top: 15px;
+    padding: 15px;
+    background-color: white;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+}
+
+#newReport{
+    text-align:center
+    font-weight: bold;
+    background-color: snow;
 }
index 0fbeb6f0e4a92c6aeb6b8a29a8f37c648b468ce8..eabc556ffa201ff0e2ce6c55b200dc94bd305206 100644 (file)
@@ -1,4 +1,3 @@
-
 <!--
 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)
@@ -7,7 +6,7 @@ GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-
 {% load static %}
 <html>
     <head>
-        <title>Pinpoint-Report</title>
+        <title>{% block title %}Georeport{% endblock %}</title>
         <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
             integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
             crossorigin=""/>
@@ -17,12 +16,18 @@ GNU General Public License v3.0 (see LICSENE or https://www.gnu.org/license/gpl-
             integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
             crossorigin="">
         </script>
-        <link rel="stylesheet" href="{% static 'georeport/style.css' %}"/>
+
+        <link rel="stylesheet" href="{% static 'georeport/style.css' %}">
+
     </head>
+
     <body>
-        <h1>Pinpoint-Report</h1>
-        <div id="map"></div>
-        <script src="{% static 'georeport/mapsetup.js' %}"> </script>
+        <div class="container">
+            <h1>Pinpoint</h1>
+            <div id="map"></div>
+            <script src="{% static 'georeport/mapsetup.js' %}"></script>
+            {% block body %}
+            {% endblock %}
+        </div>
     </body>
 </html>
-
diff --git a/georeport/templates/georeport/category.html b/georeport/templates/georeport/category.html
new file mode 100644 (file)
index 0000000..32850b1
--- /dev/null
@@ -0,0 +1,25 @@
+<!--
+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)
+-->
+{% extends "georeport/base.html" %}
+{% load static %}
+{% block title %}Detail {{ category.id }} {% endblock %}
+{% block body %}
+<div class="detail">
+    <h2>Category {{ category.id }}</h2>
+    <p>Name: {{ category.name }}</p>
+    {% if category.parent %}
+    <p>Supercategory:<a href={{category.parent.id }}>{{category.parent}}</a></p>
+    {%endif%}
+    {% if category.children.exists %}
+    <h3>Subcategories:</h3>
+    <ul>
+        {% for child in category.children.all %}
+            <li><a href={{ child.id }}>{{child.name}} </a></li>
+        {% endfor %}
+    </ul>
+    {% endif %}
+    <a href="{% url 'georeport:index' %}">Back</a>
+</div>
+{% endblock %}
diff --git a/georeport/templates/georeport/create.html b/georeport/templates/georeport/create.html
new file mode 100644 (file)
index 0000000..a4c3431
--- /dev/null
@@ -0,0 +1,50 @@
+<!--
+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)
+-->
+{% extends "georeport/base.html" %}
+{% load static %}
+{% block title %}New report {% endblock %}
+{% block body %} 
+
+<!--
+<form method="post" enctype="multipart/form-data">
+    {% csrf_token %}
+    {{ reportForm }}
+    <input type="submit"/>
+</form>
+-->
+<script src="{% static 'georeport/recurse_selection.js' %}"></script>
+<div class="detail">
+<form method="post" id="form" enctype="multipart/form-data">
+    {% csrf_token %}
+    <label for="title">Title:</label>
+    <input type="text" id="title" name="title" required> </br>
+    <label for="description">Description:</label>
+    <input type="text" id="description" name="description"> </br>
+    <label for="latitude">Latitude:</label>
+    <input type="number" id="latitude" name="latitude" step=0.000001 required > </br>
+    <label for="longitude">Longitude:</label>
+    <input type="number" id="longitude" name="longitude" step=0.000001 required > </br>
+    <label for="email">Email:</label>
+    <input type="email" id="email" name="email"i required > </br>
+    <label for="images">Bilder</label>
+    <input type="file" accept="images/*" name="image" id ="image" multiple> </br>
+    <select id="category" name="category" onchange="getsubcats(this)" required>
+        <option value="" disabled selected>Choose a category.</option>
+        {% for cat in categories %}
+            {% if cat.parent is none %}
+                <option value="{{cat.id}}">{{cat.name}}</option>
+            {% endif %}
+        {% endfor %}
+    </select></br>
+    <input type="submit" id="submit">
+</form>
+<script src="{% static 'georeport/retreiveCoordinates.js' %}"></script>
+</div>
+<div class="detail">
+<!-- TODO better URLS -->
+<a href="/georeport">Cancel</a>
+</div>
+{% endblock %}
+
diff --git a/georeport/templates/georeport/detail.html b/georeport/templates/georeport/detail.html
new file mode 100644 (file)
index 0000000..ef89bd6
--- /dev/null
@@ -0,0 +1,29 @@
+<!--
+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)
+-->
+{% extends "georeport/base.html" %}
+{% load static %}
+{% load georeport_extras %}
+{% block title %}Detail {{ report.id }} {% endblock %} 
+{% block body %} 
+   <div class="detail"> 
+    <h2>Report {{ report.id }}</h2>
+    <p>Title: {{ report.title }}</p>
+    <p>Description: {{report.description }}</p>
+    <p>Erstellt am : {{ report.creation_time }}</p>
+    <p>Geändert: {{ report.last_changed  }}</p>
+    <p id="p-lat" data-lat="{{ report.latitude }}">Latitude: {{ report.latitude }}</p>
+    <p id="p-lng" data-lng="{{ report.longitude }}">Longitude: {{ report.longitude }}</p>
+    <p>Status: {{ report.get_state_display }} </p>
+    <p>Kategorie: {{ report.category }} </p>
+    <a href="{% url 'index' %}">Back</a>
+    <script src="{% static 'georeport/addMarker.js' %}"></script>
+<!--    <img src="{{report.image.url}}" alt="Kein Bild vorhanden" scale=0.25>-->
+    {% for img in report.images.all %}
+        <!--<img src="{% static 'georeport/images/' %}{{img.file}}" alt={{img.alt}} scale=0.25 width=500px>-->
+        <img src={{urls|key:img.alt}} alt={{img.alt}} width=500px>
+    {% endfor %}
+</div>
+
+{% endblock %}
diff --git a/georeport/templates/georeport/index.html b/georeport/templates/georeport/index.html
new file mode 100644 (file)
index 0000000..a03ceb9
--- /dev/null
@@ -0,0 +1,36 @@
+<!--
+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)
+-->
+{% extends "georeport/base.html" %}
+{% load static %}
+{% block title %}Index{% endblock %}
+{% block body %}
+<div class="detail" id="newReport"><h3><a href="create">New Report</a></h3></div>
+<div class="content">
+    <div class="list">
+        <h2>Reports </h2>
+        <ul>
+            <!-- List with published reports -->
+            {% for report in report_list %}
+                {% if report.published %}
+                    <li><a href="{{ report.id }}">{{ report.title }}</a></li>
+                    <script>
+                        let marker{{report.id}} = L.marker([{{report.latitude}},{{report.longitude}}]);
+                        marker{{report.id}}.addTo(map);
+                    </script>
+                {% endif %}
+            {% endfor %}
+        </ul>
+    </div>
+    <div class="list">
+        <h2>Categories</h2>
+        <ul>
+            {% for category in category_list %}
+            <li><a href="category/{{ category.id }}">{{ category.name }}</a></li>
+            {% endfor %}
+        </ul>
+    </div>
+</div>
+
+{% endblock %}
index ab074cbda2791283ae0345b3430cde1e05f66274..ae3c2bd40777de5f05dc114b06acdece6d00f38d 100644 (file)
@@ -1,19 +1,21 @@
 from . import views
 from django.urls import path
 # TODO: Adjust to open311
-#  /services: -> List with Categories  <- GET
-#  /sercvice/{id} -> single Category <- GET
+#  /services: -> List with Categories  <- GET ✅
+#  /sercvice/{id} -> single Category <- GET ✅
 #  /requests -> Create a new Request <- POST
 #  /requests -> Get all Requests <- GET
 #  /requests/{id} -> Get a specific Request <- GET
 
+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_details, name="category"),
-    #  # TODO
-    #  path("category/<int:id>/children", views.get_subcategories, name="subcategories"),
+    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("<str:b64nonce>/<str:b64ct>", views.finish_link, name="finish"),
 ]
index eb5c355ac0581a8fcc72218298cee15a3b3c098a..7c2f731f2bf1766a63a0b835e872ffa82106743c 100644 (file)
@@ -7,25 +7,95 @@ Each view is associated with a url in urls.py.
 A view takes a request and creates a respond for the request.
 """
 
-from django.shortcuts import render
-from django.views.decorators.http import require_safe
+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 .models import Category, Report
 
 
 @require_safe
-def index(request):
+def index(request) -> HttpResponse:
     """
     Function which handles request going to "/georeport".
+
+    Returns:
+        HttpResponse
+    """
+    reports = Report.objects.all()  # type: ignore Attribute object unknown
+    categories = Category.objects.all()  # type: ignore Attribute object unknown
+
+    return render(
+        request,
+        "georeport/index.html",
+        context={"report_list": reports, "category_list": categories},
+    )
+
+
+@require_GET
+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.
+
+    Arguments:
+        request: HttpRequest
+
+        id: int
+            Integer-identifier of the category, from which the subcategories shall be send.
+            If it is not provided, all categories are included in the response
+
+    Returns:
+        JsonResponse: Contains categories as data
     """
-    return render(request, "georeport/index.html")
+    if id is None:
+        cats = Category.objects.all()  # type:ignore Object attribute unknown
+    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}
+    return JsonResponse(data)
 
 
-# TODO: Category-List
 # TODO: Category-Detail
-# TODO: Subcategories
+
+
+def category_detail_view(request, id) -> HttpResponse:
+    """
+    Function to handle requests to see information about a single category identified by id
+
+    Arguments:
+        request: HttpRequest
+        id: int
+            Integer-identifier of the category to be seen.
+    """
+    cat = get_object_or_404(Category, pk=id)
+
+    # Check if the user is allowed to view the category
+    user = request.user
+    allowed_user = cat.user.all()
+    allowed_groups = cat.groups.all()
+    allowed = False
+    if user.is_superuser:
+        allowed = True
+    if user in allowed_user:
+        allowed = True
+
+    for group in allowed_groups:
+        if user in group.user_set.all():
+            allowed = True
+
+    if allowed:
+        return render(request, "georeport/category.html", context={"categroy": cat})
+
+    else:
+        raise PermissionDenied
+
 
 # TODO: Report-List
 # TODO: Create-Report
 
-# TODO: Detailview Report
+# TODO: Detail-View Report
 
 # TODO: Finish Link
index 2b35eaf729c5999a1d9563020000c91f68edfa99..5a4719c20da54bc6e93bb50aa231011cddd85dd7 100644 (file)
@@ -56,7 +56,7 @@ ROOT_URLCONF = "pinpoint_report.urls"
 TEMPLATES = [
     {
         "BACKEND": "django.template.backends.django.DjangoTemplates",
-        "DIRS": [],
+        "DIRS": [BASE_DIR / "templates"],
         "APP_DIRS": True,
         "OPTIONS": {
             "context_processors": [
@@ -124,6 +124,9 @@ STATIC_URL = "static/"
 
 DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
 
+INTERNAL_IPS = [
+    "127.0.0.1",
+]
 TESTING = "test" in sys.argv
 
 if not TESTING:
index 201171c1f8f6f79a3014e3a0c99d5933fe9afcc6..7fb96fa4cbb94eac4c1172776f43ed52098dbd77 100644 (file)
@@ -16,13 +16,14 @@ Including another URLconf
 """
 
 from django.contrib import admin
-from django.urls import path
+from django.urls import path, include
 from django.conf import settings
 from debug_toolbar.toolbar import debug_toolbar_urls
 
 urlpatterns = [
     path("admin/", admin.site.urls),
+    path("georeport/", include("georeport.urls")),
 ]
 
-if settings.TESTING:
+if not settings.TESTING:
     urlpatterns = [*urlpatterns] + debug_toolbar_urls()