issue submission forms

This commit is contained in:
Laura Klünder 2019-12-24 17:28:41 +01:00
parent e7fc0f24ae
commit 52c02a4101
18 changed files with 652 additions and 79 deletions

View file

@ -283,7 +283,7 @@ def create_editor_form(editor_model):
'outside', 'can_search', 'can_describe', 'geometry', 'single', 'altitude', 'short_label',
'origin_space', 'target_space', 'data', 'comment', 'slow_down_factor',
'extra_seconds', 'speed', 'description', 'speed_up', 'description_up', 'enter_description',
'level_change_description', 'base_mapdata_accessible',
'level_change_description', 'base_mapdata_accessible', 'can_report_missing',
'label_settings', 'label_override', 'min_zoom', 'max_zoom', 'font_size',
'allow_levels', 'allow_spaces', 'allow_areas', 'allow_pois', 'left', 'top', 'right', 'bottom']
field_names = [field.name for field in editor_model._meta.get_fields() if not field.one_to_many]

View file

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-24 03:50+0100\n"
"POT-Creation-Date: 2019-12-24 17:22+0100\n"
"PO-Revision-Date: 2019-12-22 00:44+0100\n"
"Last-Translator: Jenny Danzmayr <mail@evilscientress.at>\n"
"Language-Team: \n"
@ -42,7 +42,7 @@ msgstr "Nich angemeldet."
msgid "Logout successful."
msgstr "Login erfolgreich."
#: c3nav/api/models.py:14
#: c3nav/api/models.py:14 c3nav/mapdata/models/report.py:79
msgid "secret"
msgstr "secret"
@ -514,6 +514,7 @@ msgstr "zurück"
#: c3nav/control/templates/control/user.html:73
#: c3nav/mapdata/models/access.py:77 c3nav/mapdata/models/geometry/space.py:385
#: c3nav/mapdata/models/report.py:58 c3nav/mapdata/models/report.py:109
#: c3nav/site/models.py:17
msgid "author"
msgstr "Autor"
@ -788,8 +789,8 @@ msgid "Change Set"
msgstr "Änderungsset"
#: c3nav/editor/models/changedobject.py:35 c3nav/editor/models/changeset.py:39
#: c3nav/editor/views/changes.py:266 c3nav/site/models.py:14
#: c3nav/site/models.py:54
#: c3nav/editor/views/changes.py:266 c3nav/mapdata/models/report.py:56
#: c3nav/site/models.py:14 c3nav/site/models.py:54
msgid "created"
msgstr "erstellt"
@ -863,7 +864,7 @@ msgstr "letzte Statusänderung"
#: c3nav/editor/models/changeset.py:48 c3nav/mapdata/models/base.py:64
#: c3nav/mapdata/models/graph.py:38 c3nav/mapdata/models/locations.py:256
#: c3nav/mapdata/models/locations.py:421 c3nav/mapdata/utils/locations.py:344
#: c3nav/mapdata/models/locations.py:423 c3nav/mapdata/utils/locations.py:344
msgid "Title"
msgstr "Titel"
@ -871,7 +872,8 @@ msgstr "Titel"
msgid "Description"
msgstr "Beschreibung"
#: c3nav/editor/models/changeset.py:51
#: c3nav/editor/models/changeset.py:51 c3nav/mapdata/models/report.py:66
#: c3nav/mapdata/models/report.py:113
msgid "assigned to"
msgstr "zugewiesen"
@ -898,7 +900,7 @@ msgid_plural "%(num)d objects changed"
msgstr[0] "%(num)d Objekt geändert"
msgstr[1] "%(num)d Objekte geändert"
#: c3nav/editor/models/changesetupdate.py:12
#: c3nav/editor/models/changesetupdate.py:12 c3nav/mapdata/models/report.py:108
msgid "datetime"
msgstr "Zeitpunkt"
@ -1150,7 +1152,7 @@ msgid "Log out"
msgstr "Abmelden"
#: c3nav/editor/templates/editor/fragment_nav.html:23
#: c3nav/editor/views/account.py:27 c3nav/site/views.py:227
#: c3nav/editor/views/account.py:27 c3nav/site/views.py:232
msgid "Log in"
msgstr "Anmelden"
@ -1277,7 +1279,7 @@ msgid "Activate direct editing"
msgstr "Direktes Bearbeiten aktivieren"
#: c3nav/editor/templates/editor/user.html:54 c3nav/editor/views/account.py:85
#: c3nav/site/templates/site/account.html:29 c3nav/site/views.py:292
#: c3nav/site/templates/site/account.html:29 c3nav/site/views.py:297
msgid "Change password"
msgstr "Passwort ändern"
@ -1298,11 +1300,11 @@ msgid "All recent change sets"
msgstr "Alle kürzlichen Änderungssets"
#: c3nav/editor/views/account.py:30 c3nav/editor/views/account.py:61
#: c3nav/site/views.py:234 c3nav/site/views.py:269
#: c3nav/site/views.py:239 c3nav/site/views.py:274
msgid "Create new account"
msgstr "Neues Konto erstellen"
#: c3nav/editor/views/account.py:75 c3nav/site/views.py:283
#: c3nav/editor/views/account.py:75 c3nav/site/views.py:288
msgid "Password successfully changed."
msgstr "Passwort erfolgreich geändert."
@ -1509,7 +1511,7 @@ msgstr "Invalides GeoJSON."
msgid "Could not clean geometry."
msgstr "Konnte Geometrie nicht bereinigen."
#: c3nav/mapdata/forms.py:54
#: c3nav/mapdata/forms.py:55
#, python-brace-format
msgid "You have to choose a value for {field} in at least one language."
msgstr "Du must das Feld {field} in mindestens einer Sprache ausfüllen."
@ -1679,9 +1681,11 @@ msgstr "Werte zurücksetzen"
msgid "save result to the stats directory"
msgstr "Ergebnis im stats-Ordner speichern"
#: c3nav/mapdata/models/access.py:23
#: c3nav/mapdata/models/access.py:23 c3nav/mapdata/models/report.py:59
#: c3nav/mapdata/models/report.py:110
#: c3nav/site/templates/site/report_detail.html:9
msgid "open"
msgstr "öffnen"
msgstr "offen"
#: c3nav/mapdata/models/access.py:24
msgid "Groups"
@ -1723,8 +1727,8 @@ msgstr "Zugangserlaubnis-Token"
msgid "Access Permission Tokens"
msgstr "Zugangserlaubnis-Token"
#: c3nav/mapdata/models/access.py:133 c3nav/site/views.py:79
#: c3nav/site/views.py:326
#: c3nav/mapdata/models/access.py:133 c3nav/site/views.py:84
#: c3nav/site/views.py:331
msgid "Area successfully unlocked."
msgid_plural "Areas successfully unlocked."
msgstr[0] "Bereich erfolgreich freigeschaltet."
@ -1976,6 +1980,7 @@ msgstr "Zielraum"
#: c3nav/mapdata/models/geometry/space.py:308
#: c3nav/mapdata/models/geometry/space.py:347 c3nav/mapdata/models/graph.py:48
#: c3nav/mapdata/models/report.py:63
msgid "description"
msgstr "Beschreibung"
@ -2000,6 +2005,7 @@ msgid "Cross descriptions"
msgstr "Durchschreitungsbeschreibungen"
#: c3nav/mapdata/models/geometry/space.py:386
#: c3nav/mapdata/models/report.py:111
msgid "comment"
msgstr "Kommentar"
@ -2159,7 +2165,7 @@ msgstr "name eines material icons"
msgid "searchable"
msgstr "suchbar"
#: c3nav/mapdata/models/locations.py:162 c3nav/mapdata/models/locations.py:321
#: c3nav/mapdata/models/locations.py:162 c3nav/mapdata/models/locations.py:323
msgid "Location Groups"
msgstr "Ortgruppen"
@ -2215,7 +2221,7 @@ msgstr "Ortgruppenkategorie"
msgid "Location Group Categories"
msgstr "Ortgruppenkategorien"
#: c3nav/mapdata/models/locations.py:309 c3nav/mapdata/models/locations.py:342
#: c3nav/mapdata/models/locations.py:309 c3nav/mapdata/models/locations.py:344
msgid "Category"
msgstr "Kategorie"
@ -2228,65 +2234,162 @@ msgid "unless location specifies otherwise"
msgstr "kann von Orten überschrieben werden"
#: c3nav/mapdata/models/locations.py:315
msgid "for missing locations"
msgstr "for fehlende Orte"
#: c3nav/mapdata/models/locations.py:316
msgid "can be used when reporting a missing location"
msgstr "kann beim melden eines fehlenden Ortges genutzt werden"
#: c3nav/mapdata/models/locations.py:317
msgid "background color"
msgstr "Hintergrundfarbe"
#: c3nav/mapdata/models/locations.py:320
#: c3nav/mapdata/models/locations.py:322
msgid "Location Group"
msgstr "Ortgruppe"
#: c3nav/mapdata/models/locations.py:344 c3nav/mapdata/models/locations.py:359
#: c3nav/mapdata/models/locations.py:346 c3nav/mapdata/models/locations.py:361
msgid "color"
msgstr "Farbe"
#: c3nav/mapdata/models/locations.py:345
#: c3nav/mapdata/models/locations.py:347
msgid "priority"
msgstr "Priorität"
#: c3nav/mapdata/models/locations.py:355
#: c3nav/mapdata/models/locations.py:357
msgid "search"
msgstr "suchen"
#: c3nav/mapdata/models/locations.py:357
#: c3nav/mapdata/models/locations.py:359
msgid "describe"
msgstr "beschreiben"
#: c3nav/mapdata/models/locations.py:361
#: c3nav/mapdata/models/locations.py:363
msgid "internal"
msgstr "intern"
#: c3nav/mapdata/models/locations.py:378
#: c3nav/mapdata/models/locations.py:380
#, python-brace-format
msgid "{category_title}, {num_locations}"
msgstr "{category_title}, {num_locations}"
#: c3nav/mapdata/models/locations.py:380
#: c3nav/mapdata/models/locations.py:382
#, python-format
msgid "%(num)d location"
msgid_plural "%(num)d locations"
msgstr[0] "%(num)d Ort"
msgstr[1] "%(num)d Orte"
#: c3nav/mapdata/models/locations.py:403
#: c3nav/mapdata/models/locations.py:405
msgid "target"
msgstr "Ziel"
#: c3nav/mapdata/models/locations.py:422
#: c3nav/mapdata/models/locations.py:424
msgid "min zoom"
msgstr "Mindestzoom"
#: c3nav/mapdata/models/locations.py:425
#: c3nav/mapdata/models/locations.py:427
msgid "max zoom"
msgstr "Maximalzoom"
#: c3nav/mapdata/models/locations.py:428
#: c3nav/mapdata/models/locations.py:430
msgid "font size"
msgstr "Schriftgröße"
#: c3nav/mapdata/models/locations.py:444 c3nav/mapdata/models/locations.py:445
#: c3nav/mapdata/models/locations.py:446 c3nav/mapdata/models/locations.py:447
msgid "Label Settings"
msgstr "Labeleinstellungen"
#: c3nav/mapdata/models/report.py:52
msgid "location issue"
msgstr "Ortfehler"
#: c3nav/mapdata/models/report.py:53
msgid "missing location"
msgstr "fehlender Ort"
#: c3nav/mapdata/models/report.py:54
msgid "route issue"
msgstr "Routenfehler"
#: c3nav/mapdata/models/report.py:57
msgid "category"
msgstr "Kategorie"
#: c3nav/mapdata/models/report.py:60
msgid "last_update"
msgstr "letzte Anderung"
#: c3nav/mapdata/models/report.py:61
msgid "title"
msgstr "Titel"
#: c3nav/mapdata/models/report.py:62
msgid "a short title for your report"
msgstr "ein kurzer Titel für deine Meldung"
#: c3nav/mapdata/models/report.py:64
msgid "tell us precisely what's wrong"
msgstr "Sag uns im Detail was falsch ist"
#: c3nav/mapdata/models/report.py:68
msgid "location"
msgstr "Ort"
#: c3nav/mapdata/models/report.py:69
msgid "coordinates"
msgstr "Koordinaten"
#: c3nav/mapdata/models/report.py:70
msgid "origin"
msgstr "Start"
#: c3nav/mapdata/models/report.py:71
msgid "destination"
msgstr "Ziel"
#: c3nav/mapdata/models/report.py:72
msgid "route options"
msgstr "Routenoptionen"
#: c3nav/mapdata/models/report.py:74
msgid "new location title"
msgstr "Neuer Ortstitel"
#: c3nav/mapdata/models/report.py:75
msgid "you have to supply a title in at least one language"
msgstr "Du must das Feld {field} in mindestens einer Sprache ausfüllen."
#: c3nav/mapdata/models/report.py:76
msgid "location groups"
msgstr "Ortgruppen"
#: c3nav/mapdata/models/report.py:78
msgid "select all groups that apply, if any"
msgstr ""
#: c3nav/mapdata/models/report.py:86
#: c3nav/site/templates/site/report_detail.html:6
msgid "Report"
msgstr "Meldung"
#: c3nav/mapdata/models/report.py:87
msgid "Reports"
msgstr "Meldungen"
#: c3nav/mapdata/models/report.py:114
msgid "public"
msgstr "öffentlich"
#: c3nav/mapdata/models/report.py:117
msgid "Report update"
msgstr "Meldungsupdate"
#: c3nav/mapdata/models/report.py:118
msgid "Report updates"
msgstr "Meldungsupdates"
#: c3nav/mapdata/models/source.py:18
msgid "Source"
msgstr "Vorlage"
@ -2408,7 +2511,7 @@ msgstr "Unerreichbarer Ort."
msgid "No route found."
msgstr "Keine Route gefunden."
#: c3nav/routing/api.py:110
#: c3nav/routing/api.py:109
msgid "Invalid scan data."
msgstr "Invalide Scandaten."
@ -2456,65 +2559,66 @@ msgstr "Invalider Scan. Unerlaubte Frequenz."
msgid "Invalid Scan. Invalid last timestamp."
msgstr "Invalider Scan. Letzter Zeitstempel ungültig."
#: c3nav/routing/models.py:21 c3nav/routing/models.py:22
#: c3nav/routing/models.py:20 c3nav/routing/models.py:21
#: c3nav/site/templates/site/fragment_report_meta.html:17
#: c3nav/site/templates/site/map.html:177
msgid "Route options"
msgstr "Routenoptionen"
#: c3nav/routing/models.py:33
#: c3nav/routing/models.py:32
msgid "Routing mode"
msgstr "Routemodus"
#: c3nav/routing/models.py:34
#: c3nav/routing/models.py:33
msgid "fastest"
msgstr "schnellste"
#: c3nav/routing/models.py:34
#: c3nav/routing/models.py:33
msgid "shortest"
msgstr "kürzeste"
#: c3nav/routing/models.py:38
#: c3nav/routing/models.py:37
msgid "Walk speed"
msgstr "Gehgeschwindigkeit"
#: c3nav/routing/models.py:39
#: c3nav/routing/models.py:38
msgid "slow"
msgstr "langsam"
#: c3nav/routing/models.py:39
#: c3nav/routing/models.py:38
msgid "default"
msgstr "standard"
#: c3nav/routing/models.py:39
#: c3nav/routing/models.py:38
msgid "fast"
msgstr "schnell"
#: c3nav/routing/models.py:45
#: c3nav/routing/models.py:44
msgid "allow"
msgstr "erlaubt"
#: c3nav/routing/models.py:47
#: c3nav/routing/models.py:46
msgid "avoid upwards"
msgstr "aufwärts vermeiden"
#: c3nav/routing/models.py:48
#: c3nav/routing/models.py:47
msgid "avoid downwards"
msgstr "abwärts vermeiden"
#: c3nav/routing/models.py:49
#: c3nav/routing/models.py:48
msgid "avoid completely"
msgstr "komplett vermeiden"
#: c3nav/routing/models.py:51
#: c3nav/routing/models.py:50
msgid "avoid"
msgstr "vermeiden"
#: c3nav/routing/models.py:144
#: c3nav/routing/models.py:143
#, python-format
msgid "Unknown route option: %s"
msgstr "Unbekannte Routenoption: %s"
#: c3nav/routing/models.py:148
#: c3nav/routing/models.py:147
#, python-format
msgid "Invalid value for route option %s."
msgstr "Invalider Wert für Routenoption %s."
@ -2658,6 +2762,26 @@ msgstr "Du bist angemeldet als <strong>%(username)s</strong>."
msgid "You can access the control panel."
msgstr "Du kannst das Control Panel betreten."
#: c3nav/site/templates/site/fragment_report_meta.html:3
msgid "You are reporting an issue with the following location:"
msgstr "Du meldest einen Fehler beim folgenden Ort:"
#: c3nav/site/templates/site/fragment_report_meta.html:6
msgid "You are reporting an missing location at the following position:"
msgstr "Du meldest einen fehlenden Ort an der folgenden Stelle."
#: c3nav/site/templates/site/fragment_report_meta.html:9
msgid "You are reporting an issue with the following route:"
msgstr "Du meldest einen Fehler bei der folgenden Route:"
#: c3nav/site/templates/site/fragment_report_meta.html:11
msgid "Origin"
msgstr "Start"
#: c3nav/site/templates/site/fragment_report_meta.html:14
msgid "Destination"
msgstr "Ziel"
#: c3nav/site/templates/site/language.html:7
msgid "Pick your language"
msgstr "Sprache wählen"
@ -2697,11 +2821,11 @@ msgstr "Route von hier aus"
#: c3nav/site/templates/site/map.html:52 c3nav/site/templates/site/map.html:149
#: c3nav/site/templates/site/map.html:170
#: c3nav/site/templates/site/report_create.html:6
msgid "Report issue"
msgstr "Fehler melden"
#: c3nav/site/templates/site/map.html:56 c3nav/site/templates/site/map.html:153
#| msgid "Select this location"
msgid "Report missing location"
msgstr "Fehlenden Ort melden"
@ -2770,39 +2894,70 @@ msgstr ""
msgid "open in c3nav"
msgstr "in c3nav öffnen"
#: c3nav/site/templates/site/report.html:6
msgid "Coming soon"
msgstr "Coming soon"
#: c3nav/site/templates/site/report_create.html:13
msgid "Submit"
msgstr "Absenden"
#: c3nav/site/views.py:71 c3nav/site/views.py:318
#: c3nav/site/templates/site/report_detail.html:11
msgid "closed"
msgstr "geschlossen"
#: c3nav/site/templates/site/report_detail.html:15
msgid "anonymous submission"
msgstr "anonyme Meldung"
#: c3nav/site/templates/site/report_detail.html:17
msgid "by"
msgstr "von"
#: c3nav/site/templates/site/report_detail.html:35
msgid "(none)"
msgstr "(keine)"
#: c3nav/site/views.py:76 c3nav/site/views.py:323
msgid "You need to log in to unlock areas."
msgstr "Du musst dich anmelden um Bereiche freizuschalten."
#: c3nav/site/views.py:206
#: c3nav/site/views.py:211
msgid "Areas could not be unlocked because the token has expired."
msgstr ""
"Zugangserlaubnis konnte nicht gewährt werden weil der Code abgelaufen ist."
#: c3nav/site/views.py:249
#: c3nav/site/views.py:254
msgid "account creation is currently disabled."
msgstr "Benutzerregistrierung ist momentan deaktiviert."
#: c3nav/site/views.py:311
#: c3nav/site/views.py:316
msgid "This token does not exist or was already redeemed."
msgstr "Dieser Code existiert nicht oder wurde bereits eingelöst."
#: c3nav/site/views.py:331
#: c3nav/site/views.py:336
msgid "Unlock area"
msgid_plural "Unlock areas"
msgstr[0] "Bereich freischalten"
msgstr[1] "Bereiche freischalten"
#: c3nav/site/views.py:332
#: c3nav/site/views.py:337
msgid "You have been invited to unlock the following area:"
msgid_plural "You have been invited to unlock the following areas:"
msgstr[0] "Du wurdest eingeladen, den folgenden Bereich freizuschalten:"
msgstr[1] "Du wurdest eingeladen, die folgenden Bereiche freizuschalten:"
#: c3nav/site/views.py:408
msgid "Your report was submitted."
msgstr "Deine Meldiung wurde abgesendet."
#: c3nav/site/views.py:411
msgid "You can keep track of it from your user dashboard."
msgstr "Du kannst sie in deinerm Benutzerdashboard verfolgen."
#: c3nav/site/views.py:413
msgid "You can keep track of it by revisiting the public URL mentioned below."
msgstr "Du kannst sie mit dem unten angegebenen öffentlichen link verfolgen."
#~ msgid "Coming soon"
#~ msgstr "Coming soon"
#~ msgid "You can not edit this object."
#~ msgstr "Du kannst dieses Objekt nicht bearbeiten."

View file

@ -41,7 +41,8 @@ class I18nModelFormMixin(ModelForm):
new_fields[sub_field_name] = CharField(label=field_title,
required=False,
initial=values[language].strip(),
max_length=model_field.i18n_max_length)
max_length=model_field.i18n_max_length,
help_text=form_field.help_text)
if has_values:
self.i18n_fields.append((model_field, values))

View file

@ -0,0 +1,84 @@
# Generated by Django 2.2.8 on 2019-12-24 15:52
import c3nav.mapdata.fields
import c3nav.mapdata.models.report
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('mapdata', '0077_obstacle_altitude'),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True, verbose_name='created')),
('category', models.CharField(choices=[('location-issue', 'location issue'), ('missing-location', 'missing location'), ('route-issue', 'route issue')], db_index=True, max_length=20, verbose_name='category')),
('open', models.BooleanField(default=True, verbose_name='open')),
('last_update', models.DateTimeField(auto_now=True, verbose_name='last_update')),
('title', models.CharField(default='', help_text='a short title for your report', max_length=100, verbose_name='title')),
('description', models.TextField(default='', help_text="tell us precisely what's wrong", max_length=1000, verbose_name='description')),
('coordinates_id', models.CharField(max_length=48, null=True, verbose_name='coordinates')),
('origin_id', models.CharField(max_length=48, null=True, verbose_name='origin')),
('destination_id', models.CharField(max_length=48, null=True, verbose_name='destination')),
('route_options', models.CharField(max_length=128, null=True, verbose_name='route options')),
('created_title', c3nav.mapdata.fields.I18nField(fallback_any=True, help_text='you have to supply a title in at least one language', plural_name='titles', verbose_name='new location title')),
('secret', models.CharField(default=c3nav.mapdata.models.report.get_report_secret, max_length=32, verbose_name='secret')),
('assigned_to', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assigned_reports', to=settings.AUTH_USER_MODEL, verbose_name='assigned to')),
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='report', to=settings.AUTH_USER_MODEL, verbose_name='author')),
],
options={
'verbose_name': 'Report',
'verbose_name_plural': 'Reports',
'default_related_name': 'report',
},
),
migrations.AlterModelOptions(
name='lineobstacle',
options={'default_related_name': 'lineobstacles', 'ordering': ('altitude', 'height'), 'verbose_name': 'Line Obstacle', 'verbose_name_plural': 'Line Obstacles'},
),
migrations.AlterModelOptions(
name='obstacle',
options={'default_related_name': 'obstacles', 'ordering': ('altitude', 'height'), 'verbose_name': 'Obstacle', 'verbose_name_plural': 'Obstacles'},
),
migrations.AddField(
model_name='locationgroup',
name='can_report_missing',
field=models.BooleanField(default=False, help_text='can be used when reporting a missing location', verbose_name='for missing locations'),
),
migrations.CreateModel(
name='ReportUpdate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('datetime', models.DateTimeField(auto_now_add=True, verbose_name='datetime')),
('open', models.NullBooleanField(verbose_name='open')),
('comment', models.TextField(verbose_name='comment')),
('public', models.BooleanField(verbose_name='public')),
('assigned_to', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='report_update_assigns', to=settings.AUTH_USER_MODEL, verbose_name='assigned to')),
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reportupdate', to=settings.AUTH_USER_MODEL, verbose_name='author')),
('report', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reportupdate', to='mapdata.Report')),
],
options={
'verbose_name': 'Report update',
'verbose_name_plural': 'Report updates',
'default_related_name': 'reportupdate',
},
),
migrations.AddField(
model_name='report',
name='created_groups',
field=models.ManyToManyField(blank=True, help_text='select all groups that apply, if any', limit_choices_to={'can_report_missing': True}, related_name='report', to='mapdata.LocationGroup', verbose_name='location groups'),
),
migrations.AddField(
model_name='report',
name='location',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reports', to='mapdata.LocationSlug', verbose_name='location'),
),
]

View file

@ -312,6 +312,8 @@ class LocationGroup(Location, models.Model):
label_settings = models.ForeignKey('mapdata.LabelSettings', null=True, blank=True, on_delete=models.PROTECT,
verbose_name=_('label settings'),
help_text=_('unless location specifies otherwise'))
can_report_missing = models.BooleanField(default=False, verbose_name=_('for missing locations'),
help_text=_('can be used when reporting a missing location'))
color = models.CharField(null=True, blank=True, max_length=32, verbose_name=_('background color'))
objects = LocationGroupManager()

View file

@ -0,0 +1,119 @@
import string
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _
from c3nav.mapdata.fields import I18nField
from c3nav.mapdata.utils.locations import get_location_by_id_for_request
def get_report_secret():
return get_random_string(32, string.ascii_letters)
class LocationById():
def __init__(self):
super().__init__()
self.name = None
self.cached_id = None
self.cached_value = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner=None):
value_id = getattr(instance, self.name+'_id')
if value_id is None:
self.cached_pk = None
self.cached_value = None
return None
if value_id == self.cached_id:
return self.cached_value
value = get_location_by_id_for_request(value_id, getattr(instance, 'request', None))
if value is None:
raise ObjectDoesNotExist
self.cached_id = value_id
self.cached_value = value
return value
def __set__(self, instance, value):
self.cached_id = value.pk
self.cached_value = value
setattr(instance, self.name+'_id', value.pk)
class Report(models.Model):
CATEGORIES = (
('location-issue', _('location issue')),
('missing-location', _('missing location')),
('route-issue', _('route issue')),
)
created = models.DateTimeField(auto_now_add=True, verbose_name=_('created'))
category = models.CharField(max_length=20, db_index=True, choices=CATEGORIES, verbose_name=_('category'))
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.PROTECT, verbose_name=_('author'))
open = models.BooleanField(default=True, verbose_name=_('open'))
last_update = models.DateTimeField(auto_now=True, verbose_name=_('last_update'))
title = models.CharField(max_length=100, default='', verbose_name=_('title'),
help_text=_('a short title for your report'))
description = models.TextField(max_length=1000, default='', verbose_name=_('description'),
help_text=_('tell us precisely what\'s wrong'))
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.PROTECT,
related_name='assigned_reports', verbose_name=_('assigned to'))
location = models.ForeignKey('mapdata.LocationSlug', null=True, on_delete=models.SET_NULL,
related_name='reports', verbose_name=_('location'))
coordinates_id = models.CharField(_('coordinates'), null=True, max_length=48)
origin_id = models.CharField(_('origin'), null=True, max_length=48)
destination_id = models.CharField(_('destination'), null=True, max_length=48)
route_options = models.CharField(_('route options'), null=True, max_length=128)
created_title = I18nField(_('new location title'), plural_name='titles', blank=False, fallback_any=True,
help_text=_('you have to supply a title in at least one language'))
created_groups = models.ManyToManyField('mapdata.LocationGroup', verbose_name=_('location groups'), blank=True,
limit_choices_to={'can_report_missing': True},
help_text=_('select all groups that apply, if any'))
secret = models.CharField(_('secret'), max_length=32, default=get_report_secret)
coordinates = LocationById()
origin = LocationById()
destination = LocationById()
class Meta:
verbose_name = _('Report')
verbose_name_plural = _('Reports')
default_related_name = 'report'
@property
def form_cls(self):
from c3nav.site.forms import ReportMissingLocationForm, ReportIssueForm
return ReportMissingLocationForm if self.category == 'missing-location' else ReportIssueForm
@classmethod
def qs_for_request(cls, request):
if request.user.is_superuser:
# todo: permissions!
return cls.objects.all()
elif request.user.is_authenticated:
return cls.objects.filter(author=request.user)
else:
return cls.objects.none()
class ReportUpdate(models.Model):
report = models.ForeignKey(Report, on_delete=models.CASCADE)
datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('datetime'))
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.PROTECT, verbose_name=_('author'))
open = models.NullBooleanField(verbose_name=_('open'))
comment = models.TextField(verbose_name=_('comment'))
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.PROTECT,
related_name='report_update_assigns', verbose_name=_('assigned to'))
public = models.BooleanField(verbose_name=_('public'))
class Meta:
verbose_name = _('Report update')
verbose_name_plural = _('Report updates')
default_related_name = 'reportupdate'

View file

@ -418,3 +418,6 @@ class CustomLocation:
@cached_property
def subtitle(self):
return self.title_subtitle[1]
def get_icon(self):
return self.icon

View file

@ -174,6 +174,7 @@ class RouteOptions(models.Model):
for choice_name, choice_title in field.choices
],
'value': self[name],
'value_display': dict(field.choices)[self[name]],
}
for name, field in self.get_fields().items()
]
@ -181,6 +182,12 @@ class RouteOptions(models.Model):
def serialize_string(self):
return ','.join('%s=%s' % (key, val) for key, val in self.data.items())
@classmethod
def unserialize_string(cls, data):
return RouteOptions(
data=dict(item.split('=') for item in data.split(','))
)
def save(self, *args, **kwargs):
if self.request is None or self.request.user.is_authenticated:
self.user = self.request.user

16
src/c3nav/site/forms.py Normal file
View file

@ -0,0 +1,16 @@
from django.forms import ModelForm
from c3nav.mapdata.forms import I18nModelFormMixin
from c3nav.mapdata.models.report import Report
class ReportIssueForm(I18nModelFormMixin, ModelForm):
class Meta:
model = Report
fields = ['title', 'description']
class ReportMissingLocationForm(I18nModelFormMixin, ModelForm):
class Meta:
model = Report
fields = ['title', 'description', 'created_title', 'created_groups']

View file

@ -547,6 +547,9 @@ main.show-options #resultswrapper #route-options {
padding: 5px 10px 5px 53px;
height: 55px;
}
.location.location-form-value {
margin: -10px -10px 5px -10px;
}
.location .icon {
font-size: 36px;
position: absolute;
@ -1256,6 +1259,25 @@ main .narrow p, main .narrow form, main .narrow button {
main .narrow form button {
width: 100%;
}
main form > p, #modal form > p {
margin-bottom: 15px;
> :last-child {
margin-bottom: 0;
}
.helptext {
display: block;
margin-top: -15px;
font-style: italic;
color: #999999;
}
textarea {
resize: none;
height: 100px;
}
select {
height: 100px;
}
}
.user-permissions-form label {
font-weight: 400;

View file

@ -14,7 +14,7 @@
<form method="post" action="{{ request.path_info }}?{{ request.META.QUERY_STRING }}">
{% csrf_token %}
{{ form }}
{{ form.as_p }}
<button type="submit">{{ title }}</button>
{% if bottom_link_url %}
<a href="{{ bottom_link_url }}?{{ request.META.QUERY_STRING }}">{{ bottom_link_text }}</a>

View file

@ -0,0 +1,5 @@
<div class="location{% if form_value %} location-form-value{% endif %}">
<i class="icon material-icons">{% if location.get_icon %}{{ location.get_icon }}{% else %}place{% endif %}</i>
<span>{{ location.title }}</span>
<small>{% if add_subtitle %}{{ add_subtitle }}, {% endif %}{{ location.subtitle }}</small>
</div>

View file

@ -0,0 +1,22 @@
{% load i18n %}
{% if report.category == 'location-issue' %}
<p><strong>{% trans 'You are reporting an issue with the following location:' %}</strong></p>
{% include 'site/fragment_location.html' with form_value=1 location=report.location %}
{% elif report.category == 'missing-location' %}
<p><strong>{% trans 'You are reporting an missing location at the following position:' %}</strong></p>
{% include 'site/fragment_location.html' with form_value=1 location=report.coordinates add_subtitle=report.coordinates_id %}
{% elif report.category == 'route-issue' %}
<p><strong>{% trans 'You are reporting an issue with the following route:' %}</strong></p>
<label>{% trans 'Origin' %}:</label>
{% include 'site/fragment_location.html' with form_value=1 location=report.origin %}
<label>{% trans 'Destination' %}:</label>
{% include 'site/fragment_location.html' with form_value=1 location=report.destination %}
<label>{% trans 'Route options' %}:</label>
{% for option in options.serialize %}
{{ option.label }}: {{ option.value_display }}<br>
{% endfor %}
</p>
{% endif %}

View file

@ -1,10 +0,0 @@
{% extends 'site/base.html' %}
{% load i18n %}
{% block content %}
<main class="account">
<h2>{% trans 'Coming soon' %}</h2>
{% include 'site/fragment_messages.html' %}
</main>
{% endblock %}

View file

@ -0,0 +1,16 @@
{% extends 'site/base.html' %}
{% load i18n %}
{% block content %}
<main class="account">
<h2>{% trans 'Report issue' %}</h2>
{% include 'site/fragment_messages.html' %}
<form method="post" action="{{ request.path_info }}?{{ request.META.QUERY_STRING }}">
{% csrf_token %}
{% include 'site/fragment_report_meta.html' %}
{{ form.as_p }}
<button type="submit">{% trans 'Submit' %}</button>
</form>
</main>
{% endblock %}

View file

@ -0,0 +1,44 @@
{% extends 'site/base.html' %}
{% load i18n %}
{% block content %}
<main class="account">
<h2>{% trans 'Report' %}: {{ report.title }}</h2>
<p><em>
{% if report.open %}
<strong class="green">{% trans 'open' %}</strong>
{% else %}
<strong class="red">{% trans 'closed' %}</strong>
{% endif %}
{% if report.author %}
{% trans 'anonymous submission' %}
{% else %}
{% trans 'by' %} {{ request.author.username }}
{% endif %}
{{ report.created }}
</em></p>
{% include 'site/fragment_messages.html' %}
{% include 'site/fragment_report_meta.html' %}
{% for field in form %}
{% if field.name != 'title' %}
<p>
<strong>{{ field.label }}:</strong><br>
{% if field.name == 'description' %}
{{ report.description | linebreaksbr }}
{% elif field.name == 'created_groups' %}
{% for group in report.created_groups.all %}
{{ group.title }}<br>
{% empty %}
<em>{% trans '(none)' %}</em>
{% endfor %}
{% else %}
{{ field.value }}
{% endif %}
</p>
{% endif %}
{% endfor %}
</main>
{% endblock %}

View file

@ -1,7 +1,7 @@
from django.conf.urls import url
from c3nav.site.views import (about_view, access_redeem_view, account_view, change_password_view, choose_language,
login_view, logout_view, map_index, qr_code, register_view, report_view)
login_view, logout_view, map_index, qr_code, register_view, report_create, report_detail)
slug = r'(?P<slug>[a-z0-9-_.:]+)'
coordinates = r'(?P<coordinates>[a-z0-9-_:]+:-?\d+(\.\d+)?:-?\d+(\.\d+)?)'
@ -27,7 +27,10 @@ urlpatterns = [
url(r'^lang/$', choose_language, name='site.language'),
url(r'^about/$', about_view, name='site.about'),
url(r'^report/$', about_view, name='site.about'),
url(r'^report/l/%s/$' % coordinates, report_view, name='site.report'),
url(r'^report/l/(?P<location>\d+)/$', report_view, name='site.report'),
url(r'^report/r/(?P<origin>[^/]+)/(?P<destination>[^/]+)/(?P<options>[^/]+)/$', report_view, name='site.report'),
url(r'^report/(?P<pk>\d+)/$', report_detail, name='site.report_detail'),
url(r'^report/(?P<pk>\d+)/(?P<secret>[^/]+)/$', report_detail, name='site.report_detail'),
url(r'^report/l/%s/$' % coordinates, report_create, name='site.report_create'),
url(r'^report/l/(?P<location>\d+)/$', report_create, name='site.report_create'),
url(r'^report/r/(?P<origin>[^/]+)/(?P<destination>[^/]+)/(?P<options>[^/]+)/$',
report_create, name='site.report_create'),
]

View file

@ -9,11 +9,12 @@ from django.contrib.auth import login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, UserCreationForm
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import ObjectDoesNotExist, SuspiciousOperation
from django.core.serializers.json import DjangoJSONEncoder
from django.db import transaction
from django.http import HttpResponse, HttpResponseBadRequest
from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.middleware import csrf
from django.shortcuts import redirect, render
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
@ -27,9 +28,12 @@ from c3nav.mapdata.grid import grid
from c3nav.mapdata.models import Location, Source
from c3nav.mapdata.models.access import AccessPermissionToken
from c3nav.mapdata.models.locations import LocationRedirect, SpecificLocation
from c3nav.mapdata.utils.locations import get_location_by_slug_for_request, levels_by_short_label_for_request
from c3nav.mapdata.models.report import Report
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request,
levels_by_short_label_for_request)
from c3nav.mapdata.utils.user import can_access_editor, get_user_data
from c3nav.mapdata.views import set_tile_access_cookie
from c3nav.routing.models import RouteOptions
from c3nav.site.models import Announcement, SiteUpdate
@ -350,6 +354,86 @@ def about_view(request):
})
def get_report_location_for_request(pk, request):
location = get_location_by_id_for_request(pk, request)
if location is None:
raise Http404
return location
@never_cache
def report_view(request, coordinates=None, location=None, origin=None, destination=None, options=None):
return render(request, 'site/report.html', {})
def report_create(request, coordinates=None, location=None, origin=None, destination=None, options=None):
report = Report()
report.request = request
if coordinates:
report.category = 'missing-location'
report.coordinates_id = coordinates
try:
report.coordinates
except ObjectDoesNotExist:
raise Http404
elif location:
report.category = 'location-issue'
report.location = get_report_location_for_request(location, request)
if report.location is None:
raise Http404
report.location = location
elif origin:
report.category = 'route-issue'
report.origin_id = origin
report.destination_id = destination
try:
# noinspection PyStatementEffect
report.origin
# noinspection PyStatementEffect
report.destination
except ObjectDoesNotExist:
raise Http404
try:
options = RouteOptions.unserialize_string(options)
except Exception:
raise SuspiciousOperation
report.options = options.serialize_string()
if request.method == 'POST':
form = report.form_cls(instance=report, data=request.POST)
if form.is_valid():
report = form.instance
if request.user.is_authenticated:
report.author = request.user
report.save()
success_messages = [_('Your report was submitted.')]
success_kwargs = {'pk': report.pk}
if request.user.is_authenticated:
success_messages.append(_('You can keep track of it from your user dashboard.'))
else:
success_messages.append(_('You can keep track of it by revisiting the public URL mentioned below.'))
success_kwargs = {'secret': report.secret}
messages.success(request, ' '.join(str(s) for s in success_messages))
return redirect(reverse('site.report_detail', kwargs=success_kwargs))
else:
form = report.form_cls(instance=report)
return render(request, 'site/report_create.html', {
'report': report,
'options': options,
'form': form,
})
def report_detail(request, pk, secret=None):
if secret:
qs = Report.objects.filter(secret=secret)
else:
qs = Report.qs_for_request(request)
report = get_object_or_404(qs, pk=pk)
report.request = request
form = report.form_cls(instance=report)
return render(request, 'site/report_detail.html', {
'report': report,
'form': form,
})