-# Pinpoint
+# Pinpoint-Report
-Simple project, in which it is possible to make a report to a specific location
-
-planned upcoming Features:
-
-- [ ] Hierarchical Categories
- - [ ] Recursive Categories
-- [ ] User Management
- - User can be grouped
- - Groups have change access to categories
- - maybe groups enable creation of subcategories
-- [ ] Sending Mails
-- [ ] Open311 Compliance
-- [ ] Tests
-
-- [ ] Publishing and depublishing of reports
+Pinpoint-Report is a web-app, which allows to create reports for locations.
+This is done by clicking on a map to specify the location and afterwards
+fill some fields to provide information.
--- /dev/null
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--- /dev/null
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+import os
+import sys
+import django
+
+sys.path.insert(0, os.path.abspath(".."))
+os.environ["DJANGO_SETTINGS_MODULE"] = "pinpoint_report.settings"
+django.setup()
+
+project = "Pinpoint-Report"
+copyright = "2025, Jörn Menne"
+author = "Jörn Menne"
+release = "0.1"
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.duration",
+ "sphinx.ext.napoleon",
+ "sphinxcontrib.plantuml",
+ "sphinx.ext.mathjax",
+]
+
+templates_path = ["_templates"]
+exclude_patterns = []
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = "alabaster"
+html_static_path = ["_static"]
--- /dev/null
+Models
+======
+
+Here are the models used in Pinpoint-Report
+
+
--- /dev/null
+.. Pinpoint-Report documentation master file, created by
+ sphinx-quickstart on Tue Feb 4 12:52:16 2025.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Pinpoint-Report documentation
+=============================
+
+Add your content using ``reStructuredText`` syntax. See the
+`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
+documentation for details.
+
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ georeport/models
--- /dev/null
+@ECHO OFF\r
+\r
+pushd %~dp0\r
+\r
+REM Command file for Sphinx documentation\r
+\r
+if "%SPHINXBUILD%" == "" (\r
+ set SPHINXBUILD=sphinx-build\r
+)\r
+set SOURCEDIR=.\r
+set BUILDDIR=_build\r
+\r
+%SPHINXBUILD% >NUL 2>NUL\r
+if errorlevel 9009 (\r
+ echo.\r
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx\r
+ echo.installed, then set the SPHINXBUILD environment variable to point\r
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you\r
+ echo.may add the Sphinx directory to PATH.\r
+ echo.\r
+ echo.If you don't have Sphinx installed, grab it from\r
+ echo.https://www.sphinx-doc.org/\r
+ exit /b 1\r
+)\r
+\r
+if "%1" == "" goto help\r
+\r
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r
+goto end\r
+\r
+:help\r
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\r
+\r
+:end\r
+popd\r
--- /dev/null
+version: "3"
+
+services:
+ minio:
+ image: bitnami/minio
+ ports:
+ - 9000:9000
+ - 9001:9001
+ environment:
+ - MINIO_ROOT_USER=minio
+ - MINIO_ROOT_PASSWORD=minio123
+ restart: unless-stopped
-# 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)
-__version__ = "0.1.0"
-# 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, messages
-from django.urls import base
-from django.utils.translation import ngettext
+from django.contrib import admin
# Register your models here.
-
-from .models import Category, Report
-
-
-@admin.register(Report)
-class ReportAdmin(admin.ModelAdmin):
- exclude = None
- readonly_fields = ["creation_time", "last_change"]
- actions = ["make_public"]
- list_display = ["title", "category__name", "state", "published"]
-
- list_filter = ["state"]
-
- @admin.action(description="Publish selected reports.")
- def make_public(self, request, queryset):
- updated = queryset.update(published=True)
- self.message_user(
- request,
- ngettext(
- "%d report was published",
- "%d reports were published",
- updated,
- )
- % updated,
- messages.SUCCESS,
- )
-
-
-class CategoryInline(admin.TabularInline):
- model = Category
- extra = 0
- can_delete = False
-
- def has_change_permission(self, request, obj=None):
- return False
-
-
-@admin.register(Category)
-class CategoryAdmin(admin.ModelAdmin):
- exlude = None
- inlines = [CategoryInline]
-
- def has_change_permission(self, request, obj=None):
- basepermission = super().has_change_permission(request, obj)
- if obj:
- allowed = obj.group.all()
- else:
- allowed = []
-
- if basepermission and (request.user in allowed):
- return True
- return False
-
-
-class GeoreportAdminSite(admin.AdminSite):
- site_header = "My cool admin site"
-
-
-admin_site = GeoreportAdminSite(name="coolAdmin")
-admin_site.register(Report, ReportAdmin)
-admin_site.register(Category, CategoryAdmin)
+++ /dev/null
-# 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):
- class Meta:
- model = Report
- fields = ["title", "description", "latitude", "longitude", "email", "category"]
+++ /dev/null
-# Generated by Django 5.1.3 on 2024-11-30 14:05
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='Report',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=80)),
- ('creation_time', models.DateTimeField(auto_now_add=True)),
- ('last_change', models.DateTimeField(auto_now=True)),
- ('description', models.CharField(max_length=255, null=True)),
- ('latitude', models.DecimalField(decimal_places=6, max_digits=8)),
- ('longitude', models.DecimalField(decimal_places=6, max_digits=9)),
- ],
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.3 on 2024-12-02 09:15
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('georeport', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='report',
- name='state',
- field=models.IntegerField(choices=[(0, 'New'), (1, 'Finished')], default=0),
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2024-12-21 13:30
-
-import django.db.models.deletion
-import georeport.models
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('georeport', '0002_report_state'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Category',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=100)),
- ],
- ),
- migrations.AlterField(
- model_name='report',
- name='description',
- field=models.CharField(blank=True, max_length=255, null=True),
- ),
- migrations.AddField(
- model_name='report',
- name='category',
- field=models.ForeignKey(default=georeport.models.get_default_related, on_delete=django.db.models.deletion.RESTRICT, to='georeport.category'),
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-06 09:56
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ("georeport", "0003_category_alter_report_description_report_category"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="report",
- name="email",
- field=models.EmailField(default="mail@pinpoint.de", max_length=254),
- preserve_default=False,
- ),
- migrations.AddField(
- model_name="report",
- name="published",
- field=models.BooleanField(default=True),
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-06 10:04
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('georeport', '0004_report_email_report_published'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='category',
- name='parent',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='georeport.category'),
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-13 10:58
-
-import django.contrib.auth.models
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('auth', '0012_alter_user_first_name_max_length'),
- ('georeport', '0005_category_parent'),
- ]
-
- operations = [
- migrations.AlterModelOptions(
- name='category',
- options={'verbose_name_plural': 'Categories'},
- ),
- migrations.CreateModel(
- name='Group',
- fields=[
- ('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.group')),
- ('categories', models.ManyToManyField(related_name='auth_groups', to='georeport.category')),
- ],
- bases=('auth.group',),
- managers=[
- ('objects', django.contrib.auth.models.GroupManager()),
- ],
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-15 09:09
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('georeport', '0006_alter_category_options_group'),
- ]
-
- operations = [
- migrations.DeleteModel(
- name='Group',
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-20 12:04
-
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('georeport', '0007_delete_group'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.AddField(
- model_name='category',
- name='group',
- field=models.ManyToManyField(related_name='owner', to=settings.AUTH_USER_MODEL),
- ),
- ]
-# 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 asyncio import wait
from django.db import models
-from django.contrib.auth.models import Group, User, Permission
# Create your models here.
-
-
-class Category(models.Model):
- name = models.CharField(max_length=100)
- parent = models.ForeignKey(
- "self",
- on_delete=models.CASCADE,
- related_name="children",
- null=True,
- blank=True,
- )
-
- group = models.ManyToManyField(User, related_name="owner")
-
- class Meta:
- verbose_name_plural = "Categories"
-
- def __str__(self):
- return self.name
-
-
-def get_default_related():
- def get_default_related():
- return Category.objects.first().id
-
-
-class Report(models.Model):
- class State(models.IntegerChoices):
- NEW = 0
- FINISHED = 1
-
- title = models.CharField(max_length=80)
- creation_time = models.DateTimeField(auto_now_add=True)
- # TODO last change shall be set to creation_time at creation_time
- last_change = models.DateTimeField(auto_now=True)
- description = models.CharField(max_length=255, null=True, blank=True)
-
- latitude = models.DecimalField(max_digits=8, decimal_places=6)
- longitude = models.DecimalField(max_digits=9, decimal_places=6)
-
- state = models.IntegerField(choices=State, default=0)
- category = models.ForeignKey(
- Category, on_delete=models.RESTRICT, default=get_default_related
- )
-
- published = models.BooleanField(default=True)
-
- email = models.EmailField()
-
- # TODO add status
- #
- def __str__(self):
- return self.title
+++ /dev/null
-/*
- * 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)
-*/
-var lat_input = document.getElementById("latitude");
-var lng_input = document.getElementById("longitude");
-let marker = L.marker();
-
-/*
- * set to 6 decimals points to get a precision of around 10cm
- * according to https://en.wikipedia.org/wiki/Decimal_degrees
-*/
-const precision = 6
-
-
-lat_input.addEventListener("change", () => {
- marker.setLatLng([lat_input.value, lng_input.value])
- i.addTo(map);
-
-});
-lng_input.addEventListener("change", () => {
- marker.setLatLng([lat_input.value, lng_input.value])
- .addTo(map);
-
-});
-
-function onMapClick(e) {
- marker.setLatLng(e.latlng)
- .addTo(map);
-
- lat_input.value = e.latlng.lat.toFixed(precision);
- lng_input.value = e.latlng.lng.toFixed(precision);
-}
-
-map.on("click", onMapClick);
-
+++ /dev/null
-/*
- * 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)
-*/
-var marker = L.marker();
-
-var lat = document.getElementById("p-lat").dataset.lat;
-var lng = document.getElementById("p-lng").dataset.lng;
-
-marker.setLatLng([lat, lng])
- .addTo(map);
+++ /dev/null
-/*
- * 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)
-*/
-var map = L.map("map").setView([51.7173, 8.753557], 15);
-L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
- maxZoom: 19,
- attribution: "© <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
-}).addTo(map);
+++ /dev/null
-/*
- * 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)
-*/
-var level = 0
-function getsubcats(element) {
- const id = element.id;;
- var level = 0;
-
- if (id == "category")
- level = 0;
- else
- level = id
-
- console.log(level);
- const rootselect = document.getElementById(id);
-
- let = url = `category/${rootselect.value}/children`;
-
- const form = document.getElementById("form");
- const submit = document.getElementById("submit");
- console.log(url);
- fetch(url)
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP Error: Status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- console.log(data);
- level++;
-
- let subcats = data["subcategories"];
- if (subcats.length == 0) {
- let oldselect = document.getElementById(level);
- rootselect.name = "category";
- if (oldselect) {
- form.removeChild(oldselect);
- oldselect.remove();
- form.removeChild(submit);
- form.appendChild(submit);
- }
- return
- }
-
- let select = document.getElementById(level);
- if (select != null)
- select.remove();
- 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";
- rootselect.name = "root";
-
- var option = document.createElement("option");
- option.value = "";
- option.innerHTML = "Subcategory";
- option.disabled = true;
- option.selected = true;
- select.appendChild(option);
-
-
- console.log(subcats);
- for (var cat of subcats) {
- option = document.createElement("option");
- option.value = cat.id;
- option.innerText = cat.name;
- select.appendChild(option);
- }
-
- form.removeChild(submit);
- form.appendChild(select);
- form.appendChild(submit);
-
- console.log(level);
- });
-
-}
-
-// TODO: Better IDs for new selections
-// TODO: Labels for selection*/
-// TODO: Tidy up
-
+++ /dev/null
-#map {
- width: 50%;
- border: 3px solid;
- margin: auto;
- border-radius: 25px;
-}
+++ /dev/null
-<!--
-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)
--->
-<!DOCTYPE html>
-{% load static %}
-<html>
- <head>
- <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=""/>
-
- <!-- Make sure you put this AFTER Leaflet's CSS -->
- <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
- integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
- crossorigin="">
- </script>
-
- <style>
- #map { height: 500px;}
- </style>
- <link rel="stylesheet" href="{% static 'georeport/style.css' %}"
-
- </head>
-
- <body>
-
- <div id="map"></div>
- <script src="{% static 'georeport/mapsetup.js' %}"></script>
- {% block body %}
- {% endblock %}
- </body>
-</html>
+++ /dev/null
-<!--
-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 %}
- <h1>Categoriy {{ category.id }}</h1>
- <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 'index' %}">Back</a>
-{% endblock %}
+++ /dev/null
-<!--
-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">
- {% csrf_token %}
- {{ reportForm }}
- <input type="submit"/>
-</form>-->
-<script src="{% static 'georeport/recurse_selection.js' %}"></script>
-<form method="post" id="form">
- {% 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="category">Category</label>
- <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/create_Report.js' %}"></script>
-
-<!-- TODO better URLS -->
-<a href="/georeport">Cancel</a>
-{% endblock %}
-
+++ /dev/null
-<!--
-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 {{ report.id }} {% endblock %}
-{% block body %}
- <h1>Report {{ report.id }}</h1>
- <p>Title: {{ report.title }}</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/details.js' %}"></script>
-{% endblock %}
+++ /dev/null
-<!--
-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 %}
-<h1>Reports </h1>
-<a href="create">New Report</a>
-<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>
-
-<h2>Categories</h2>
-<ul>
- {% for category in category_list %}
- <li><a href="category/{{ category.id }}">{{ category.name }}</a></li>
- {% endfor %}
-</ul>
-
-{% endblock %}
-# 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 . import views
from django.urls import path
+# TODO: Adjust to open311
+# /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
-from . import views
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("<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("<str:b64nonce>/<str:b64ct>", views.finish_link, name="finish"),
]
-# 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.http import HttpResponseForbidden, JsonResponse
-from django.shortcuts import get_object_or_404, redirect, render
-
-from .forms import ReportForm
-import json
-
# Create your views here.
-from .models import Category, Report
-import pdb
-
-
-def index(request):
- reports = Report.objects.all()
- categories = Category.objects.all()
- return render(
- request,
- "georeport/index.html",
- context={"report_list": reports, "category_list": categories},
- )
-
-def details(request, id):
- report = get_object_or_404(Report, pk=id)
- if report.published:
- return render(request, "georeport/detail.html", context={"report": report})
- else:
- return HttpResponseForbidden("The report is not published")
+# TODO: Index-Views
+# TODO: Category-List
+# TODO: Report-List
+# TODO: Create-Report
+# TODO: DetailView
+# TODO: Category-Detail
+# TODO: Supcategories
-def category_details(request, id):
- category = get_object_or_404(Category, pk=id)
- user = request.user
+from django.http import HttpResponse
- allowed = category.group.all()
- if user in allowed or user.is_superuser:
- return render(
- request, "georeport/category.html", context={"category": category}
- )
- else:
- return HttpResponseForbidden("Not allowed")
-
-
-def create(request):
- if request.method == "POST":
- post = request.POST
- report = {}
- report["title"] = post["title"]
- report["longitude"] = post["longitude"]
- report["latitude"] = post["latitude"]
- report["category"] = post["category"]
- report["email"] = post["email"]
-
- reportForm = ReportForm(report)
- # reportForm = ReportForm(request.POST)
-
- # TODO Inputvalidation
- reportForm.save()
- return redirect("index")
- else:
- reportForm = ReportForm()
-
- return render(
- request,
- "georeport/create.html",
- context={"reportForm": reportForm, "categories": Category.objects.all()},
- )
-
-
-def get_subcategories(request, id):
- subcats = Category.objects.filter(parent__id=id)
- data = [{"id": cat.id, "name": cat.name} for cat in subcats]
- data = {"subcategories": data}
- return JsonResponse(data)
+def index(request):
+ return HttpResponse(b"Pinpoint-Report")
def main():
"""Run administrative tasks."""
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pinpoint.settings')
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pinpoint_report.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
"""
-ASGI config for pinpoint project.
+ASGI config for pinpoint_report project.
It exposes the ASGI callable as a module-level variable named ``application``.
from django.core.asgi import get_asgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pinpoint.settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pinpoint_report.settings')
application = get_asgi_application()
"""
-Django settings for pinpoint project.
+Django settings for pinpoint_report project.
-Generated by 'django-admin startproject' using Django 5.1.3.
+Generated by 'django-admin startproject' using Django 5.1.5.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
+import sys
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = "django-insecure-&7#5doz#@q$^jtjn!^*fmta$=eaqjd@8mtf(1=^=lt8*xen(+g"
+SECRET_KEY = "django-insecure-^f3zw!cfp(mkh()na5(i-#s3472ue*n7@m34crljq82e66#_0m"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = [".localhost", "127.0.0.1", "[::1]"]
# Application definition
INSTALLED_APPS = [
"georeport.apps.GeoreportConfig",
- "polls.apps.PollsConfig",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
- "rest_framework",
- "snippets",
- "debug_toolbar",
]
MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
- "debug_toolbar.middleware.DebugToolbarMiddleware",
]
-ROOT_URLCONF = "pinpoint.urls"
+ROOT_URLCONF = "pinpoint_report.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
- "DIRS": [BASE_DIR / "templates"],
+ "DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
},
]
-WSGI_APPLICATION = "pinpoint.wsgi.application"
+WSGI_APPLICATION = "pinpoint_report.wsgi.application"
# Database
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
-INTERNAL_IPS = [
- "127.0.0.1",
-]
-REST_FRAMEWORK = {
- "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
- "PAGE_SIZE": 10,
-}
+TESTING = "test" in sys.argv
+
+if not TESTING:
+ INSTALLED_APPS = [
+ *INSTALLED_APPS,
+ "debug_toolbar",
+ ]
+ MIDDLEWARE = [
+ "debug_toolbar.middleware.DebugToolbarMiddleware",
+ *MIDDLEWARE,
+ ]
"""
-URL configuration for pinpoint project.
+URL configuration for pinpoint_report project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.1/topics/http/urls/
"""
from django.contrib import admin
-from django.urls import path, include
+from django.urls import path
+from django.conf import settings
from debug_toolbar.toolbar import debug_toolbar_urls
-from georeport.admin import admin_site
-
urlpatterns = [
path("admin/", admin.site.urls),
- path("myadmin/", admin_site.urls),
- path("snippets/", include("snippets.urls")),
- path("georeport/", include("georeport.urls")),
- path("polls/", include("polls.urls")),
-] + debug_toolbar_urls()
+]
+
+if settings.TESTING:
+ urlpatterns = [*urlpatterns] + debug_toolbar_urls()
"""
-WSGI config for pinpoint project.
+WSGI config for pinpoint_report project.
It exposes the WSGI callable as a module-level variable named ``application``.
from django.core.wsgi import get_wsgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pinpoint.settings')
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pinpoint_report.settings')
application = get_wsgi_application()
+++ /dev/null
-from django.contrib import admin
-
-# Register your models here.
-
-from .models import *
-
-
-class ChoiceInLine(admin.TabularInline):
- model = Choice
- extra = 0
-
-
-@admin.register(Question)
-class QuestionAdmin(admin.ModelAdmin):
- fieldsets = [
- (None, {"fields": ["question_text"]}),
- ("Date Information", {"fields": ["pub_date"]}),
- ]
- inlines = [ChoiceInLine]
-
- list_display = ["question_text", "pub_date", "was_published_recently"]
- search_fields = ["question_text"]
-
-
-admin.site.register(Choice)
+++ /dev/null
-from django.apps import AppConfig
-
-
-class PollsConfig(AppConfig):
- default_auto_field = 'django.db.models.BigAutoField'
- name = 'polls'
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-16 18:35
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ]
-
- operations = [
- migrations.CreateModel(
- name='Question',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('question_text', models.CharField(max_length=100)),
- ('pub_date', models.DateTimeField(verbose_name='date published')),
- ],
- ),
- migrations.CreateModel(
- name='Choice',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('choice_text', models.CharField(max_length=200)),
- ('votes', models.IntegerField(default=0)),
- ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.question')),
- ],
- ),
- ]
+++ /dev/null
-from django.db import models
-import datetime
-from django.utils import timezone
-from django.contrib import admin
-
-
-class Question(models.Model):
- question_text = models.CharField(max_length=100)
- pub_date = models.DateTimeField("date published")
-
- @admin.display(boolean=True, ordering="pub_date", description="Published recently?")
- def was_published_recently(self):
- now = timezone.now()
- return now - datetime.timedelta(days=1) <= self.pub_date <= now
-
- def __str__(self): # type: ignore
- return self.question_text
-
-
-class Choice(models.Model):
- question = models.ForeignKey(Question, on_delete=models.CASCADE)
- choice_text = models.CharField(max_length=200)
- votes = models.IntegerField(default=0) # type:ignore
-
- def __str__(self): # type: ignore
- return self.choice_text
+++ /dev/null
-<html>
- <head>
- <title>Detail</title>
- </head>
- <body>
- <form action="{% url 'polls:vote' question.id %}" method="post">
- {% csrf_token %}
- <fieldset>
- <legend><h1>{{ question.question_text }}</h1></legend>
- {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
- {% for choice in question.choice_set.all %}
- <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
- <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
- {% endfor %}
- </fieldset>
- <input type="submit" value="Vote">
- </form>
- </body>
-</html>
+++ /dev/null
-<html>
- <head>
- <title>Index</title>
- </head>
- <body>
- {% if latest_question_list %}
- <ul>
- {% for question in latest_question_list %}
- <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text}}</a></li>
- {% endfor %}
- </ul>
- {% else %}
- <p>No polls</p>
- {% endif %}
-
- </body>
-</html>
+++ /dev/null
-<html>
- <head>
- <title>Results</title>
- </head>
- <body>
- <h1>{{question.question_text}}</h1>
-
- <ul>
- {% for choice in question.choice_set.all %}
- <li>{{choice.choice_text}} -- {{choice.votes }} vote {{choice.votes|pluralize}}</li>
- {% endfor %}
- </ul>
- <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
- </body>
-</html>
+++ /dev/null
-from django.urls import reverse
-from django.test import TestCase
-
-import datetime
-from django.utils import timezone
-
-from .models import Question
-
-
-class QuestionModelTests(TestCase):
- def test_was_published_recently_with_future_question(self):
- time = timezone.now() + datetime.timedelta(days=30)
- future_question = Question(pub_date=time)
- self.assertIs(future_question.was_published_recently(), False)
-
- def test_was_published_recently_with_old_question(self):
- """
- was_published_recently() returns False for questions whose pub_date
- is older than 1 day.
- """
- time = timezone.now() - datetime.timedelta(days=1, seconds=1)
- old_question = Question(pub_date=time)
- self.assertIs(old_question.was_published_recently(), False)
-
- def test_was_published_recently_with_recent_question(self):
- """
- was_published_recently() returns True for questions whose pub_date
- is within the last day.
- """
- time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
- recent_question = Question(pub_date=time)
- self.assertIs(recent_question.was_published_recently(), True)
-
-
-def create_question(question_text, days):
- """
- Create a question with the given `question_text` and published the
- given number of `days` offset to now (negative for questions published
- in the past, positive for questions that have yet to be published).
- """
- time = timezone.now() + datetime.timedelta(days=days)
- return Question.objects.create(question_text=question_text, pub_date=time)
-
-
-class QuestionIndexViewTests(TestCase):
- def test_no_questoin(self):
- response = self.client.get(reverse("polls:index"))
- self.assertEqual(response.status_code, 200)
- self.assertContains(response, "No polls")
- self.assertQuerySetEqual(response.context["latest_question_list"], [])
-
- def test_past_question(self):
- """
- Questions with a pub_date in the past are displayed on the
- index page.
- """
- question = create_question(question_text="Past question.", days=-30)
- response = self.client.get(reverse("polls:index"))
- self.assertQuerySetEqual(
- response.context["latest_question_list"],
- [question],
- )
-
- def test_future_question(self):
- """
- Questions with a pub_date in the future aren't displayed on
- the index page.
- """
- create_question(question_text="Future question.", days=30)
- response = self.client.get(reverse("polls:index"))
- self.assertContains(response, "No polls")
- self.assertQuerySetEqual(response.context["latest_question_list"], [])
-
- def test_future_question_and_past_question(self):
- """
- Even if both past and future questions exist, only past questions
- are displayed.
- """
- question = create_question(question_text="Past question.", days=-30)
- create_question(question_text="Future question.", days=30)
- response = self.client.get(reverse("polls:index"))
- self.assertQuerySetEqual(
- response.context["latest_question_list"],
- [question],
- )
-
- def test_two_past_questions(self):
- """
- The questions index page may display multiple questions.
- """
- question1 = create_question(question_text="Past question 1.", days=-30)
- question2 = create_question(question_text="Past question 2.", days=-5)
- response = self.client.get(reverse("polls:index"))
- self.assertQuerySetEqual(
- response.context["latest_question_list"],
- [question2, question1],
- )
-
-
-class QuestionDetailViewTests(TestCase):
- def test_future_question(self):
- """
- The detail view of a question with a pub_date in the future
- returns a 404 not found.
- """
- future_question = create_question(question_text="Future question.", days=5)
- url = reverse("polls:detail", args=(future_question.id,))
- response = self.client.get(url)
- self.assertEqual(response.status_code, 404)
-
- def test_past_question(self):
- """
- The detail view of a question with a pub_date in the past
- displays the question's text.
- """
- past_question = create_question(question_text="Past Question.", days=-5)
- url = reverse("polls:detail", args=(past_question.id,))
- response = self.client.get(url)
- self.assertContains(response, past_question.question_text)
+++ /dev/null
-from django.urls import path
-
-from . import views
-
-app_name = "polls"
-urlpatterns = [
- # ex: /polls/
- path("", views.IndexView.as_view(), name="index"),
- # ex: /polls/5/
- path("<int:pk>/", views.DetailView.as_view(), name="detail"),
- # ex: /polls/5/results/
- path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
- # ex: /polls/5/vote/
- path("<int:question_id>/vote/", views.vote, name="vote"),
-]
+++ /dev/null
-from django.shortcuts import get_object_or_404, render
-
-from django.http import HttpResponse, HttpResponseRedirect
-from django.urls import reverse
-
-from polls.models import Question, Choice
-from django.db.models import F
-from django.views import generic
-
-from django.utils import timezone
-
-
-class IndexView(generic.ListView):
- template_name = "polls/index.html"
- context_object_name = "latest_question_list"
-
- def get_queryset(self):
- return Question.objects.filter(pub_date__lte=timezone.now()).order_by(
- "-pub_date"
- )[:5]
-
-
-class DetailView(generic.DetailView):
- model = Question
- template_name = "polls/detail.html"
-
- def get_queryset(self):
- return Question.objects.filter(pub_date__lte=timezone.now())
-
-
-class ResultsView(generic.DetailView):
- model = Question
- template_name = "polls/results.html"
-
-
-def vote(request, question_id):
- question = get_object_or_404(Question, pk=question_id)
- try:
- selected_choice = question.choice_set.get(pk=request.POST["choice"])
- except (KeyError, Choice.DoesNotExist):
- # Redisplay the question voting form.
- return render(
- request,
- "polls/detail.html",
- {
- "question": question,
- "error_message": "You didn't select a choice.",
- },
- )
- else:
- selected_choice.votes = F("votes") + 1
- selected_choice.save()
- # Always return an HttpResponseRedirect after successfully dealing
- # with POST data. This prevents data from being posted twice if a
- # user hits the Back button.
- return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
+# Django
django
django-debug-toolbar
+++ /dev/null
-from django.contrib import admin
-
-# Register your models here.
+++ /dev/null
-from django.apps import AppConfig
-
-
-class SnippetsConfig(AppConfig):
- default_auto_field = 'django.db.models.BigAutoField'
- name = 'snippets'
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-15 09:36
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Snippet',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created', models.DateTimeField(auto_now_add=True)),
- ('title', models.CharField(blank=True, default='', max_length=100)),
- ('code', models.TextField()),
- ('linenos', models.BooleanField(default=False)),
- ('language', models.CharField(choices=[('abap', 'ABAP'), ('abnf', 'ABNF'), ('actionscript', 'ActionScript'), ('actionscript3', 'ActionScript 3'), ('ada', 'Ada'), ('adl', 'ADL'), ('agda', 'Agda'), ('aheui', 'Aheui'), ('alloy', 'Alloy'), ('ambienttalk', 'AmbientTalk'), ('amdgpu', 'AMDGPU'), ('ampl', 'Ampl'), ('androidbp', 'Soong'), ('ansys', 'ANSYS parametric design language'), ('antlr', 'ANTLR'), ('antlr-actionscript', 'ANTLR With ActionScript Target'), ('antlr-cpp', 'ANTLR With CPP Target'), ('antlr-csharp', 'ANTLR With C# Target'), ('antlr-java', 'ANTLR With Java Target'), ('antlr-objc', 'ANTLR With ObjectiveC Target'), ('antlr-perl', 'ANTLR With Perl Target'), ('antlr-python', 'ANTLR With Python Target'), ('antlr-ruby', 'ANTLR With Ruby Target'), ('apacheconf', 'ApacheConf'), ('apl', 'APL'), ('applescript', 'AppleScript'), ('arduino', 'Arduino'), ('arrow', 'Arrow'), ('arturo', 'Arturo'), ('asc', 'ASCII armored'), ('asn1', 'ASN.1'), ('aspectj', 'AspectJ'), ('aspx-cs', 'aspx-cs'), ('aspx-vb', 'aspx-vb'), ('asymptote', 'Asymptote'), ('augeas', 'Augeas'), ('autohotkey', 'autohotkey'), ('autoit', 'AutoIt'), ('awk', 'Awk'), ('bare', 'BARE'), ('basemake', 'Base Makefile'), ('bash', 'Bash'), ('batch', 'Batchfile'), ('bbcbasic', 'BBC Basic'), ('bbcode', 'BBCode'), ('bc', 'BC'), ('bdd', 'Bdd'), ('befunge', 'Befunge'), ('berry', 'Berry'), ('bibtex', 'BibTeX'), ('blitzbasic', 'BlitzBasic'), ('blitzmax', 'BlitzMax'), ('blueprint', 'Blueprint'), ('bnf', 'BNF'), ('boa', 'Boa'), ('boo', 'Boo'), ('boogie', 'Boogie'), ('bqn', 'BQN'), ('brainfuck', 'Brainfuck'), ('bst', 'BST'), ('bugs', 'BUGS'), ('c', 'C'), ('c-objdump', 'c-objdump'), ('ca65', 'ca65 assembler'), ('cadl', 'cADL'), ('camkes', 'CAmkES'), ('capdl', 'CapDL'), ('capnp', "Cap'n Proto"), ('carbon', 'Carbon'), ('cbmbas', 'CBM BASIC V2'), ('cddl', 'CDDL'), ('ceylon', 'Ceylon'), ('cfc', 'Coldfusion CFC'), ('cfengine3', 'CFEngine3'), ('cfm', 'Coldfusion HTML'), ('cfs', 'cfstatement'), ('chaiscript', 'ChaiScript'), ('chapel', 'Chapel'), ('charmci', 'Charmci'), ('cheetah', 'Cheetah'), ('cirru', 'Cirru'), ('clay', 'Clay'), ('clean', 'Clean'), ('clojure', 'Clojure'), ('clojurescript', 'ClojureScript'), ('cmake', 'CMake'), ('cobol', 'COBOL'), ('cobolfree', 'COBOLFree'), ('codeql', 'CodeQL'), ('coffeescript', 'CoffeeScript'), ('comal', 'COMAL-80'), ('common-lisp', 'Common Lisp'), ('componentpascal', 'Component Pascal'), ('console', 'Bash Session'), ('coq', 'Coq'), ('cplint', 'cplint'), ('cpp', 'C++'), ('cpp-objdump', 'cpp-objdump'), ('cpsa', 'CPSA'), ('cr', 'Crystal'), ('crmsh', 'Crmsh'), ('croc', 'Croc'), ('cryptol', 'Cryptol'), ('csharp', 'C#'), ('csound', 'Csound Orchestra'), ('csound-document', 'Csound Document'), ('csound-score', 'Csound Score'), ('css', 'CSS'), ('css+django', 'CSS+Django/Jinja'), ('css+genshitext', 'CSS+Genshi Text'), ('css+lasso', 'CSS+Lasso'), ('css+mako', 'CSS+Mako'), ('css+mozpreproc', 'CSS+mozpreproc'), ('css+myghty', 'CSS+Myghty'), ('css+php', 'CSS+PHP'), ('css+ruby', 'CSS+Ruby'), ('css+smarty', 'CSS+Smarty'), ('css+ul4', 'CSS+UL4'), ('cuda', 'CUDA'), ('cypher', 'Cypher'), ('cython', 'Cython'), ('d', 'D'), ('d-objdump', 'd-objdump'), ('dart', 'Dart'), ('dasm16', 'DASM16'), ('dax', 'Dax'), ('debcontrol', 'Debian Control file'), ('debian.sources', 'Debian Sources file'), ('debsources', 'Debian Sourcelist'), ('delphi', 'Delphi'), ('desktop', 'Desktop file'), ('devicetree', 'Devicetree'), ('dg', 'dg'), ('diff', 'Diff'), ('django', 'Django/Jinja'), ('docker', 'Docker'), ('doscon', 'MSDOS Session'), ('dpatch', 'Darcs Patch'), ('dtd', 'DTD'), ('duel', 'Duel'), ('dylan', 'Dylan'), ('dylan-console', 'Dylan session'), ('dylan-lid', 'DylanLID'), ('earl-grey', 'Earl Grey'), ('easytrieve', 'Easytrieve'), ('ebnf', 'EBNF'), ('ec', 'eC'), ('ecl', 'ECL'), ('eiffel', 'Eiffel'), ('elixir', 'Elixir'), ('elm', 'Elm'), ('elpi', 'Elpi'), ('emacs-lisp', 'EmacsLisp'), ('email', 'E-mail'), ('erb', 'ERB'), ('erl', 'Erlang erl session'), ('erlang', 'Erlang'), ('evoque', 'Evoque'), ('execline', 'execline'), ('extempore', 'xtlang'), ('ezhil', 'Ezhil'), ('factor', 'Factor'), ('fan', 'Fantom'), ('fancy', 'Fancy'), ('felix', 'Felix'), ('fennel', 'Fennel'), ('fift', 'Fift'), ('fish', 'Fish'), ('flatline', 'Flatline'), ('floscript', 'FloScript'), ('forth', 'Forth'), ('fortran', 'Fortran'), ('fortranfixed', 'FortranFixed'), ('foxpro', 'FoxPro'), ('freefem', 'Freefem'), ('fsharp', 'F#'), ('fstar', 'FStar'), ('func', 'FunC'), ('futhark', 'Futhark'), ('gap', 'GAP'), ('gap-console', 'GAP session'), ('gas', 'GAS'), ('gcode', 'g-code'), ('gdscript', 'GDScript'), ('genshi', 'Genshi'), ('genshitext', 'Genshi Text'), ('gherkin', 'Gherkin'), ('gleam', 'Gleam'), ('glsl', 'GLSL'), ('gnuplot', 'Gnuplot'), ('go', 'Go'), ('golo', 'Golo'), ('gooddata-cl', 'GoodData-CL'), ('googlesql', 'GoogleSQL'), ('gosu', 'Gosu'), ('graphql', 'GraphQL'), ('graphviz', 'Graphviz'), ('groff', 'Groff'), ('groovy', 'Groovy'), ('gsql', 'GSQL'), ('gst', 'Gosu Template'), ('haml', 'Haml'), ('handlebars', 'Handlebars'), ('hare', 'Hare'), ('haskell', 'Haskell'), ('haxe', 'Haxe'), ('haxeml', 'Hxml'), ('hexdump', 'Hexdump'), ('hlsl', 'HLSL'), ('hsail', 'HSAIL'), ('hspec', 'Hspec'), ('html', 'HTML'), ('html+cheetah', 'HTML+Cheetah'), ('html+django', 'HTML+Django/Jinja'), ('html+evoque', 'HTML+Evoque'), ('html+genshi', 'HTML+Genshi'), ('html+handlebars', 'HTML+Handlebars'), ('html+lasso', 'HTML+Lasso'), ('html+mako', 'HTML+Mako'), ('html+myghty', 'HTML+Myghty'), ('html+ng2', 'HTML + Angular2'), ('html+php', 'HTML+PHP'), ('html+smarty', 'HTML+Smarty'), ('html+twig', 'HTML+Twig'), ('html+ul4', 'HTML+UL4'), ('html+velocity', 'HTML+Velocity'), ('http', 'HTTP'), ('hybris', 'Hybris'), ('hylang', 'Hy'), ('i6t', 'Inform 6 template'), ('icon', 'Icon'), ('idl', 'IDL'), ('idris', 'Idris'), ('iex', 'Elixir iex session'), ('igor', 'Igor'), ('inform6', 'Inform 6'), ('inform7', 'Inform 7'), ('ini', 'INI'), ('io', 'Io'), ('ioke', 'Ioke'), ('irc', 'IRC logs'), ('isabelle', 'Isabelle'), ('j', 'J'), ('jags', 'JAGS'), ('janet', 'Janet'), ('jasmin', 'Jasmin'), ('java', 'Java'), ('javascript', 'JavaScript'), ('javascript+cheetah', 'JavaScript+Cheetah'), ('javascript+django', 'JavaScript+Django/Jinja'), ('javascript+lasso', 'JavaScript+Lasso'), ('javascript+mako', 'JavaScript+Mako'), ('javascript+mozpreproc', 'Javascript+mozpreproc'), ('javascript+myghty', 'JavaScript+Myghty'), ('javascript+php', 'JavaScript+PHP'), ('javascript+ruby', 'JavaScript+Ruby'), ('javascript+smarty', 'JavaScript+Smarty'), ('jcl', 'JCL'), ('jlcon', 'Julia console'), ('jmespath', 'JMESPath'), ('js+genshitext', 'JavaScript+Genshi Text'), ('js+ul4', 'Javascript+UL4'), ('jsgf', 'JSGF'), ('jslt', 'JSLT'), ('json', 'JSON'), ('json5', 'JSON5'), ('jsonld', 'JSON-LD'), ('jsonnet', 'Jsonnet'), ('jsp', 'Java Server Page'), ('jsx', 'JSX'), ('julia', 'Julia'), ('juttle', 'Juttle'), ('k', 'K'), ('kal', 'Kal'), ('kconfig', 'Kconfig'), ('kmsg', 'Kernel log'), ('koka', 'Koka'), ('kotlin', 'Kotlin'), ('kql', 'Kusto'), ('kuin', 'Kuin'), ('lasso', 'Lasso'), ('ldapconf', 'LDAP configuration file'), ('ldif', 'LDIF'), ('lean', 'Lean'), ('lean4', 'Lean4'), ('less', 'LessCss'), ('lighttpd', 'Lighttpd configuration file'), ('lilypond', 'LilyPond'), ('limbo', 'Limbo'), ('liquid', 'liquid'), ('literate-agda', 'Literate Agda'), ('literate-cryptol', 'Literate Cryptol'), ('literate-haskell', 'Literate Haskell'), ('literate-idris', 'Literate Idris'), ('livescript', 'LiveScript'), ('llvm', 'LLVM'), ('llvm-mir', 'LLVM-MIR'), ('llvm-mir-body', 'LLVM-MIR Body'), ('logos', 'Logos'), ('logtalk', 'Logtalk'), ('lsl', 'LSL'), ('lua', 'Lua'), ('luau', 'Luau'), ('macaulay2', 'Macaulay2'), ('make', 'Makefile'), ('mako', 'Mako'), ('maple', 'Maple'), ('maql', 'MAQL'), ('markdown', 'Markdown'), ('mask', 'Mask'), ('mason', 'Mason'), ('mathematica', 'Mathematica'), ('matlab', 'Matlab'), ('matlabsession', 'Matlab session'), ('maxima', 'Maxima'), ('mcfunction', 'MCFunction'), ('mcschema', 'MCSchema'), ('meson', 'Meson'), ('mime', 'MIME'), ('minid', 'MiniD'), ('miniscript', 'MiniScript'), ('mips', 'MIPS'), ('modelica', 'Modelica'), ('modula2', 'Modula-2'), ('mojo', 'Mojo'), ('monkey', 'Monkey'), ('monte', 'Monte'), ('moocode', 'MOOCode'), ('moonscript', 'MoonScript'), ('mosel', 'Mosel'), ('mozhashpreproc', 'mozhashpreproc'), ('mozpercentpreproc', 'mozpercentpreproc'), ('mql', 'MQL'), ('mscgen', 'Mscgen'), ('mupad', 'MuPAD'), ('mxml', 'MXML'), ('myghty', 'Myghty'), ('mysql', 'MySQL'), ('nasm', 'NASM'), ('ncl', 'NCL'), ('nemerle', 'Nemerle'), ('nesc', 'nesC'), ('nestedtext', 'NestedText'), ('newlisp', 'NewLisp'), ('newspeak', 'Newspeak'), ('ng2', 'Angular2'), ('nginx', 'Nginx configuration file'), ('nimrod', 'Nimrod'), ('nit', 'Nit'), ('nixos', 'Nix'), ('nodejsrepl', 'Node.js REPL console session'), ('notmuch', 'Notmuch'), ('nsis', 'NSIS'), ('numba_ir', 'Numba_IR'), ('numpy', 'NumPy'), ('nusmv', 'NuSMV'), ('objdump', 'objdump'), ('objdump-nasm', 'objdump-nasm'), ('objective-c', 'Objective-C'), ('objective-c++', 'Objective-C++'), ('objective-j', 'Objective-J'), ('ocaml', 'OCaml'), ('octave', 'Octave'), ('odin', 'ODIN'), ('omg-idl', 'OMG Interface Definition Language'), ('ooc', 'Ooc'), ('opa', 'Opa'), ('openedge', 'OpenEdge ABL'), ('openscad', 'OpenSCAD'), ('org', 'Org Mode'), ('output', 'Text output'), ('pacmanconf', 'PacmanConf'), ('pan', 'Pan'), ('parasail', 'ParaSail'), ('pawn', 'Pawn'), ('pddl', 'PDDL'), ('peg', 'PEG'), ('perl', 'Perl'), ('perl6', 'Perl6'), ('phix', 'Phix'), ('php', 'PHP'), ('pig', 'Pig'), ('pike', 'Pike'), ('pkgconfig', 'PkgConfig'), ('plpgsql', 'PL/pgSQL'), ('pointless', 'Pointless'), ('pony', 'Pony'), ('portugol', 'Portugol'), ('postgres-explain', 'PostgreSQL EXPLAIN dialect'), ('postgresql', 'PostgreSQL SQL dialect'), ('postscript', 'PostScript'), ('pot', 'Gettext Catalog'), ('pov', 'POVRay'), ('powershell', 'PowerShell'), ('praat', 'Praat'), ('procfile', 'Procfile'), ('prolog', 'Prolog'), ('promela', 'Promela'), ('promql', 'PromQL'), ('properties', 'Properties'), ('protobuf', 'Protocol Buffer'), ('prql', 'PRQL'), ('psql', 'PostgreSQL console (psql)'), ('psysh', 'PsySH console session for PHP'), ('ptx', 'PTX'), ('pug', 'Pug'), ('puppet', 'Puppet'), ('pwsh-session', 'PowerShell Session'), ('py+ul4', 'Python+UL4'), ('py2tb', 'Python 2.x Traceback'), ('pycon', 'Python console session'), ('pypylog', 'PyPy Log'), ('pytb', 'Python Traceback'), ('python', 'Python'), ('python2', 'Python 2.x'), ('q', 'Q'), ('qbasic', 'QBasic'), ('qlik', 'Qlik'), ('qml', 'QML'), ('qvto', 'QVTO'), ('racket', 'Racket'), ('ragel', 'Ragel'), ('ragel-c', 'Ragel in C Host'), ('ragel-cpp', 'Ragel in CPP Host'), ('ragel-d', 'Ragel in D Host'), ('ragel-em', 'Embedded Ragel'), ('ragel-java', 'Ragel in Java Host'), ('ragel-objc', 'Ragel in Objective C Host'), ('ragel-ruby', 'Ragel in Ruby Host'), ('rbcon', 'Ruby irb session'), ('rconsole', 'RConsole'), ('rd', 'Rd'), ('reasonml', 'ReasonML'), ('rebol', 'REBOL'), ('red', 'Red'), ('redcode', 'Redcode'), ('registry', 'reg'), ('rego', 'Rego'), ('resourcebundle', 'ResourceBundle'), ('restructuredtext', 'reStructuredText'), ('rexx', 'Rexx'), ('rhtml', 'RHTML'), ('ride', 'Ride'), ('rita', 'Rita'), ('rng-compact', 'Relax-NG Compact'), ('roboconf-graph', 'Roboconf Graph'), ('roboconf-instances', 'Roboconf Instances'), ('robotframework', 'RobotFramework'), ('rql', 'RQL'), ('rsl', 'RSL'), ('ruby', 'Ruby'), ('rust', 'Rust'), ('sarl', 'SARL'), ('sas', 'SAS'), ('sass', 'Sass'), ('savi', 'Savi'), ('scala', 'Scala'), ('scaml', 'Scaml'), ('scdoc', 'scdoc'), ('scheme', 'Scheme'), ('scilab', 'Scilab'), ('scss', 'SCSS'), ('sed', 'Sed'), ('sgf', 'SmartGameFormat'), ('shen', 'Shen'), ('shexc', 'ShExC'), ('sieve', 'Sieve'), ('silver', 'Silver'), ('singularity', 'Singularity'), ('slash', 'Slash'), ('slim', 'Slim'), ('slurm', 'Slurm'), ('smali', 'Smali'), ('smalltalk', 'Smalltalk'), ('smarty', 'Smarty'), ('smithy', 'Smithy'), ('sml', 'Standard ML'), ('snbt', 'SNBT'), ('snobol', 'Snobol'), ('snowball', 'Snowball'), ('solidity', 'Solidity'), ('sophia', 'Sophia'), ('sp', 'SourcePawn'), ('sparql', 'SPARQL'), ('spec', 'RPMSpec'), ('spice', 'Spice'), ('splus', 'S'), ('sql', 'SQL'), ('sql+jinja', 'SQL+Jinja'), ('sqlite3', 'sqlite3con'), ('squidconf', 'SquidConf'), ('srcinfo', 'Srcinfo'), ('ssp', 'Scalate Server Page'), ('stan', 'Stan'), ('stata', 'Stata'), ('supercollider', 'SuperCollider'), ('swift', 'Swift'), ('swig', 'SWIG'), ('systemd', 'Systemd'), ('systemverilog', 'systemverilog'), ('tablegen', 'TableGen'), ('tact', 'Tact'), ('tads3', 'TADS 3'), ('tal', 'Tal'), ('tap', 'TAP'), ('tasm', 'TASM'), ('tcl', 'Tcl'), ('tcsh', 'Tcsh'), ('tcshcon', 'Tcsh Session'), ('tea', 'Tea'), ('teal', 'teal'), ('teratermmacro', 'Tera Term macro'), ('termcap', 'Termcap'), ('terminfo', 'Terminfo'), ('terraform', 'Terraform'), ('tex', 'TeX'), ('text', 'Text only'), ('thrift', 'Thrift'), ('ti', 'ThingsDB'), ('tid', 'tiddler'), ('tlb', 'Tl-b'), ('tls', 'TLS Presentation Language'), ('tnt', 'Typographic Number Theory'), ('todotxt', 'Todotxt'), ('toml', 'TOML'), ('trac-wiki', 'MoinMoin/Trac Wiki markup'), ('trafficscript', 'TrafficScript'), ('treetop', 'Treetop'), ('tsql', 'Transact-SQL'), ('tsx', 'TSX'), ('turtle', 'Turtle'), ('twig', 'Twig'), ('typescript', 'TypeScript'), ('typoscript', 'TypoScript'), ('typoscriptcssdata', 'TypoScriptCssData'), ('typoscripthtmldata', 'TypoScriptHtmlData'), ('typst', 'Typst'), ('ucode', 'ucode'), ('ul4', 'UL4'), ('unicon', 'Unicon'), ('unixconfig', 'Unix/Linux config files'), ('urbiscript', 'UrbiScript'), ('urlencoded', 'urlencoded'), ('usd', 'USD'), ('vala', 'Vala'), ('vb.net', 'VB.net'), ('vbscript', 'VBScript'), ('vcl', 'VCL'), ('vclsnippets', 'VCLSnippets'), ('vctreestatus', 'VCTreeStatus'), ('velocity', 'Velocity'), ('verifpal', 'Verifpal'), ('verilog', 'verilog'), ('vgl', 'VGL'), ('vhdl', 'vhdl'), ('vim', 'VimL'), ('visualprolog', 'Visual Prolog'), ('visualprologgrammar', 'Visual Prolog Grammar'), ('vue', 'Vue'), ('vyper', 'Vyper'), ('wast', 'WebAssembly'), ('wdiff', 'WDiff'), ('webidl', 'Web IDL'), ('wgsl', 'WebGPU Shading Language'), ('whiley', 'Whiley'), ('wikitext', 'Wikitext'), ('wowtoc', 'World of Warcraft TOC'), ('wren', 'Wren'), ('x10', 'X10'), ('xml', 'XML'), ('xml+cheetah', 'XML+Cheetah'), ('xml+django', 'XML+Django/Jinja'), ('xml+evoque', 'XML+Evoque'), ('xml+lasso', 'XML+Lasso'), ('xml+mako', 'XML+Mako'), ('xml+myghty', 'XML+Myghty'), ('xml+php', 'XML+PHP'), ('xml+ruby', 'XML+Ruby'), ('xml+smarty', 'XML+Smarty'), ('xml+ul4', 'XML+UL4'), ('xml+velocity', 'XML+Velocity'), ('xorg.conf', 'Xorg'), ('xpp', 'X++'), ('xquery', 'XQuery'), ('xslt', 'XSLT'), ('xtend', 'Xtend'), ('xul+mozpreproc', 'XUL+mozpreproc'), ('yaml', 'YAML'), ('yaml+jinja', 'YAML+Jinja'), ('yang', 'YANG'), ('yara', 'YARA'), ('zeek', 'Zeek'), ('zephir', 'Zephir'), ('zig', 'Zig'), ('zone', 'Zone')], default='python', max_length=100)),
- ('style', models.CharField(choices=[('abap', 'abap'), ('algol', 'algol'), ('algol_nu', 'algol_nu'), ('arduino', 'arduino'), ('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('coffee', 'coffee'), ('colorful', 'colorful'), ('default', 'default'), ('dracula', 'dracula'), ('emacs', 'emacs'), ('friendly', 'friendly'), ('friendly_grayscale', 'friendly_grayscale'), ('fruity', 'fruity'), ('github-dark', 'github-dark'), ('gruvbox-dark', 'gruvbox-dark'), ('gruvbox-light', 'gruvbox-light'), ('igor', 'igor'), ('inkpot', 'inkpot'), ('lightbulb', 'lightbulb'), ('lilypond', 'lilypond'), ('lovelace', 'lovelace'), ('manni', 'manni'), ('material', 'material'), ('monokai', 'monokai'), ('murphy', 'murphy'), ('native', 'native'), ('nord', 'nord'), ('nord-darker', 'nord-darker'), ('one-dark', 'one-dark'), ('paraiso-dark', 'paraiso-dark'), ('paraiso-light', 'paraiso-light'), ('pastie', 'pastie'), ('perldoc', 'perldoc'), ('rainbow_dash', 'rainbow_dash'), ('rrt', 'rrt'), ('sas', 'sas'), ('solarized-dark', 'solarized-dark'), ('solarized-light', 'solarized-light'), ('staroffice', 'staroffice'), ('stata-dark', 'stata-dark'), ('stata-light', 'stata-light'), ('tango', 'tango'), ('trac', 'trac'), ('vim', 'vim'), ('vs', 'vs'), ('xcode', 'xcode'), ('zenburn', 'zenburn')], default='friendly', max_length=100)),
- ('highlighted', models.TextField()),
- ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to=settings.AUTH_USER_MODEL)),
- ],
- options={
- 'ordering': ['created'],
- },
- ),
- ]
+++ /dev/null
-# Generated by Django 5.1.4 on 2025-01-16 18:35
-
-import django.db.models.deletion
-from django.conf import settings
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('snippets', '0001_initial'),
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.AlterField(
- model_name='snippet',
- name='owner',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='snippets', to=settings.AUTH_USER_MODEL),
- ),
- ]
+++ /dev/null
-from django.db import models
-
-from pygments.formatters import HtmlFormatter
-from pygments.lexers import get_all_lexers
-from pygments.styles import get_all_styles
-
-from pygments.lexers import get_lexer_by_name
-from pygments.formatters.html import HtmlFormatter
-from pygments import highlight
-
-
-LEXERS = [item for item in get_all_lexers() if item[1]]
-LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
-STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
-
-
-class Snippet(models.Model):
- created = models.DateTimeField(auto_now_add=True)
- title = models.CharField(max_length=100, blank=True, default="")
- code = models.TextField()
- linenos = models.BooleanField(default=False) # type: ignore
- language = models.CharField(
- choices=LANGUAGE_CHOICES, default="python", max_length=100
- )
- style = models.CharField(choices=STYLE_CHOICES, default="friendly", max_length=100)
-
- owner = models.ForeignKey(
- "auth.User", related_name="snippets", on_delete=models.CASCADE
- )
- highlighted = models.TextField()
-
- def save(self, *args, **kwargs):
- """
- Use the `pygments` library to create a highlighted HTML
- representation of the code snippet.
- """
- lexer = get_lexer_by_name(self.language)
- linenos = "table" if self.linenos else False
- options = {"title": self.title} if self.title else {}
- formatter = HtmlFormatter(
- style=self.style, linenos=linenos, full=True, **options
- )
- self.highlighted = highlight(self.code, lexer, formatter)
- super().save(*args, **kwargs)
-
- class Meta:
- ordering = ["created"]
+++ /dev/null
-from rest_framework import permissions
-
-
-class IsOwnerOrReadOnly(permissions.BasePermission):
- """
- Custom permission to only allow owner of an object to edit it.
- """
-
- def has_object_permission(self, request, view, obj):
- # Read permission are allowed to any request
- # so we'll always allow GET, HEAD, or OPTIONS requests
- if request.method in permissions.SAFE_METHODS:
- return True
-
- # Write permissions are only allowed to the owner of the snippet
- return obj.owner == request.user
+++ /dev/null
-from django.contrib.auth.models import User
-from .models import Snippet
-from rest_framework import serializers
-
-
-class SnippetSerializer(serializers.HyperlinkedModelSerializer):
- owner = serializers.ReadOnlyField(source="owner.username")
- hightlight = serializers.HyperlinkedIdentityField(
- view_name="snippet-highlight", format="html"
- )
-
- class Meta:
- model = Snippet
- fields = [
- "url",
- "id",
- "hightlight",
- "owner",
- "title",
- "code",
- "linenos",
- "language",
- "style",
- ]
-
-
-class UserSerializer(serializers.HyperlinkedModelSerializer):
- snippets = serializers.HyperlinkedRelatedField(
- many=True,
- view_name="snippet-detail",
- read_only=True,
- )
-
- class Meta:
- model = User
- fields = ["url", "id", "username", "snippets"]
+++ /dev/null
-from django.test import TestCase
-
-# Create your tests here.
+++ /dev/null
-from functools import partial
-from django.urls import path, include
-from rest_framework import renderers
-from rest_framework.decorators import renderer_classes
-from . import views
-from rest_framework.urlpatterns import format_suffix_patterns
-
-from .views import SnippetViewSet, UserViewSet
-
-from rest_framework.routers import DefaultRouter
-
-router = DefaultRouter()
-
-router.register(r"", SnippetViewSet, basename="snippet")
-router.register(r"users", UserViewSet, basename="user")
-
-urlpatterns = [path("", include(router.urls))]
+++ /dev/null
-from django.contrib.auth.models import User
-from rest_framework import permissions, renderers, viewsets
-from rest_framework.decorators import api_view, action
-from rest_framework.reverse import reverse
-from rest_framework.views import Response
-
-from snippets.permissions import IsOwnerOrReadOnly
-
-from .serializers import SnippetSerializer, UserSerializer
-from snippets import serializers
-from .models import Snippet
-
-
-@api_view(["GET"])
-def api_root(request, format=None):
- return Response(
- {
- "users": reverse("user-list", request=request, format=format),
- "snippets": reverse("snippet-list", request=request, format=format),
- }
- )
-
-
-class SnippetViewSet(viewsets.ModelViewSet):
- queryset = Snippet.objects.all() # type: ignore
- serializer_class = SnippetSerializer
- permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
-
- @action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
- def highlight(self, request, *args, **kwargs):
- snippet = self.get_object()
- return Response(snippet.highlighted)
-
- def perform_create(self, serializer):
- serializer.save(owner=self.request.user)
-
-
-class UserViewSet(viewsets.ReadOnlyModelViewSet):
- queryset = User.objects.all() # type:ignore
- serializer_class = UserSerializer