From d37a4538e750d9125c0499da7f864404e8019456 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=B6rn=20Menne?= Date: Fri, 7 Feb 2025 13:52:24 +0100 Subject: [PATCH] Readd getCategories views and adjustments for open311. --- georeport/admin.py | 40 ++++++- georeport/static/georeport/addMarker.js | 16 +++ georeport/static/georeport/mapsetup.js | 2 + .../georeport/recurseCategorySelection.js | 102 ++++++++++++++++++ .../static/georeport/retreiveCoordinates.js | 39 +++++++ georeport/static/georeport/style.css | 59 +++++++++- georeport/templates/georeport/base.html | 19 ++-- georeport/templates/georeport/category.html | 25 +++++ georeport/templates/georeport/create.html | 50 +++++++++ georeport/templates/georeport/detail.html | 29 +++++ georeport/templates/georeport/index.html | 36 +++++++ georeport/urls.py | 12 ++- georeport/views.py | 84 +++++++++++++-- pinpoint_report/settings.py | 5 +- pinpoint_report/urls.py | 5 +- 15 files changed, 498 insertions(+), 25 deletions(-) create mode 100644 georeport/static/georeport/addMarker.js create mode 100644 georeport/static/georeport/recurseCategorySelection.js create mode 100644 georeport/static/georeport/retreiveCoordinates.js create mode 100644 georeport/templates/georeport/category.html create mode 100644 georeport/templates/georeport/create.html create mode 100644 georeport/templates/georeport/detail.html create mode 100644 georeport/templates/georeport/index.html diff --git a/georeport/admin.py b/georeport/admin.py index 8c38f3f..f22a8a1 100644 --- a/georeport/admin.py +++ b/georeport/admin.py @@ -1,3 +1,41 @@ +# 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.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 index 0000000..66c6f74 --- /dev/null +++ b/georeport/static/georeport/addMarker.js @@ -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) +*/ + +/* + * 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); diff --git a/georeport/static/georeport/mapsetup.js b/georeport/static/georeport/mapsetup.js index de15e51..1f9be5c 100644 --- a/georeport/static/georeport/mapsetup.js +++ b/georeport/static/georeport/mapsetup.js @@ -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 index 0000000..1d661b2 --- /dev/null +++ b/georeport/static/georeport/recurseCategorySelection.js @@ -0,0 +1,102 @@ +/* + * Copyright: (c) 2025, Jörn Menne + * 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 index 0000000..acb4732 --- /dev/null +++ b/georeport/static/georeport/retreiveCoordinates.js @@ -0,0 +1,39 @@ +/* + * Copyright: (c) 2025, Jörn Menne + * 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); + +} diff --git a/georeport/static/georeport/style.css b/georeport/static/georeport/style.css index ecad1ba..ebca29b 100644 --- a/georeport/static/georeport/style.css +++ b/georeport/static/georeport/style.css @@ -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; } diff --git a/georeport/templates/georeport/base.html b/georeport/templates/georeport/base.html index 0fbeb6f..eabc556 100644 --- a/georeport/templates/georeport/base.html +++ b/georeport/templates/georeport/base.html @@ -1,4 +1,3 @@ - +{% extends "georeport/base.html" %} +{% load static %} +{% block title %}Detail {{ category.id }} {% endblock %} +{% block body %} +
+

Category {{ category.id }}

+

Name: {{ category.name }}

+ {% if category.parent %} +

Supercategory:{{category.parent}}

+ {%endif%} + {% if category.children.exists %} +

Subcategories:

+
    + {% for child in category.children.all %} +
  • {{child.name}}
  • + {% endfor %} +
+ {% endif %} + Back +
+{% endblock %} diff --git a/georeport/templates/georeport/create.html b/georeport/templates/georeport/create.html new file mode 100644 index 0000000..a4c3431 --- /dev/null +++ b/georeport/templates/georeport/create.html @@ -0,0 +1,50 @@ + +{% extends "georeport/base.html" %} +{% load static %} +{% block title %}New report {% endblock %} +{% block body %} + + + +
+
+ {% csrf_token %} + +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+
+ +Cancel +
+{% endblock %} + diff --git a/georeport/templates/georeport/detail.html b/georeport/templates/georeport/detail.html new file mode 100644 index 0000000..ef89bd6 --- /dev/null +++ b/georeport/templates/georeport/detail.html @@ -0,0 +1,29 @@ + +{% extends "georeport/base.html" %} +{% load static %} +{% load georeport_extras %} +{% block title %}Detail {{ report.id }} {% endblock %} +{% block body %} +
+

Report {{ report.id }}

+

Title: {{ report.title }}

+

Description: {{report.description }}

+

Erstellt am : {{ report.creation_time }}

+

Geändert: {{ report.last_changed }}

+

Latitude: {{ report.latitude }}

+

Longitude: {{ report.longitude }}

+

Status: {{ report.get_state_display }}

+

Kategorie: {{ report.category }}

+ Back + + + {% for img in report.images.all %} + + {{img.alt}} + {% endfor %} +
+ +{% endblock %} diff --git a/georeport/templates/georeport/index.html b/georeport/templates/georeport/index.html new file mode 100644 index 0000000..a03ceb9 --- /dev/null +++ b/georeport/templates/georeport/index.html @@ -0,0 +1,36 @@ + +{% extends "georeport/base.html" %} +{% load static %} +{% block title %}Index{% endblock %} +{% block body %} + +
+
+

Reports

+
    + + {% for report in report_list %} + {% if report.published %} +
  • {{ report.title }}
  • + + {% endif %} + {% endfor %} +
+
+
+

Categories

+ +
+
+ +{% endblock %} diff --git a/georeport/urls.py b/georeport/urls.py index ab074cb..ae3c2bd 100644 --- a/georeport/urls.py +++ b/georeport/urls.py @@ -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("", views.details, name="detail"), # path("create", views.create, name="create"), - # path("category/", views.category_details, name="category"), - # # TODO - # path("category//children", views.get_subcategories, name="subcategories"), + 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("/", views.finish_link, name="finish"), ] diff --git a/georeport/views.py b/georeport/views.py index eb5c355..7c2f731 100644 --- a/georeport/views.py +++ b/georeport/views.py @@ -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 diff --git a/pinpoint_report/settings.py b/pinpoint_report/settings.py index 2b35eaf..5a4719c 100644 --- a/pinpoint_report/settings.py +++ b/pinpoint_report/settings.py @@ -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: diff --git a/pinpoint_report/urls.py b/pinpoint_report/urls.py index 201171c..7fb96fa 100644 --- a/pinpoint_report/urls.py +++ b/pinpoint_report/urls.py @@ -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() -- 2.39.5