overhaul entire report process

This commit is contained in:
Laura Klünder 2024-03-24 17:38:24 +01:00
parent 482da3b244
commit 0d91a71b9f
12 changed files with 206 additions and 45 deletions

View file

@ -29,7 +29,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='locationgroup', model_name='locationgroup',
name='description', name='description',
field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, help_text='to aid with selection in the report form', plural_name='descriptions', verbose_name='description'), field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, fallback_value="", help_text='to aid with selection in the report form', plural_name='descriptions', verbose_name='description'),
), ),
migrations.AddField( migrations.AddField(
model_name='locationgroup', model_name='locationgroup',
@ -39,7 +39,7 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='locationgroup', model_name='locationgroup',
name='report_help_text', name='report_help_text',
field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, help_text='to explain the report form or rejection', plural_name='report_help_texts', verbose_name='report help text'), field=c3nav.mapdata.fields.I18nField(blank=True, fallback_any=True, fallback_value="", help_text='to explain the report form or rejection', plural_name='report_help_texts', verbose_name='report help text'),
), ),
migrations.RunPython(forwards_func, backwards_func), migrations.RunPython(forwards_func, backwards_func),
migrations.RemoveField( migrations.RemoveField(

View file

@ -368,9 +368,9 @@ class LocationGroup(Location, models.Model):
default=CanReportMissing.DONT_OFFER, max_length=16) default=CanReportMissing.DONT_OFFER, max_length=16)
description = I18nField(_('description'), plural_name='descriptions', blank=True, fallback_any=True, description = I18nField(_('description'), plural_name='descriptions', blank=True, fallback_any=True,
help_text=_('to aid with selection in the report form')) fallback_value="", help_text=_('to aid with selection in the report form'))
report_help_text = I18nField(_('report help text'), plural_name='report_help_texts', blank=True, fallback_any=True, report_help_text = I18nField(_('report help text'), plural_name='report_help_texts', blank=True, fallback_any=True,
help_text=_('to explain the report form or rejection')) fallback_value="", help_text=_('to explain the report form or rejection'))
color = models.CharField(null=True, blank=True, max_length=32, verbose_name=_('background color')) color = models.CharField(null=True, blank=True, max_length=32, verbose_name=_('background color'))
hub_import_type = models.CharField(max_length=100, verbose_name=_('hub import type'), null=True, blank=True, hub_import_type = models.CharField(max_length=100, verbose_name=_('hub import type'), null=True, blank=True,

View file

@ -242,7 +242,7 @@ def get_route(request, parameters: RouteParametersSchema):
request=parameters, request=parameters,
options=_new_serialize_route_options(options), options=_new_serialize_route_options(options),
options_form=options.serialize(), options_form=options.serialize(),
report_issue_url=reverse('site.report_create', kwargs={ report_issue_url=reverse('site.report_start', kwargs={
'origin': parameters.origin, 'origin': parameters.origin,
'destination': parameters.destination, 'destination': parameters.destination,
'options': options.serialize_string(), 'options': options.serialize_string(),

View file

@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _
from c3nav.api.models import Secret from c3nav.api.models import Secret
from c3nav.mapdata.forms import I18nModelFormMixin from c3nav.mapdata.forms import I18nModelFormMixin
from c3nav.mapdata.models.locations import Position from c3nav.mapdata.models.locations import Position, LocationGroup
from c3nav.mapdata.models.report import Report, ReportUpdate from c3nav.mapdata.models.report import Report, ReportUpdate
@ -26,8 +26,22 @@ class DeleteAccountForm(Form):
class ReportMissingLocationForm(I18nModelFormMixin, ModelForm): class ReportMissingLocationForm(I18nModelFormMixin, ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, group=None, request=None, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, initial={"created_groups": [group] if group else []}, **kwargs)
if group:
self.fields['created_groups'].disabled = True
self.fields['created_groups'].queryset = LocationGroup.objects.filter(pk=group.pk)
else:
exists = LocationGroup.qs_for_request(request).filter(
can_report_missing=LocationGroup.CanReportMissing.MULTIPLE
).exists()
if exists:
self.fields['created_groups'].queryset = LocationGroup.qs_for_request(request).filter(
can_report_missing=LocationGroup.CanReportMissing.MULTIPLE
)
else:
self.fields['created_groups'].queryset = LocationGroup.objects.none()
self.fields['created_groups'].widget = self.fields['created_groups'].hidden_widget()
self.fields['created_groups'].label_from_instance = lambda obj: obj.title self.fields['created_groups'].label_from_instance = lambda obj: obj.title
class Meta: class Meta:

View file

@ -152,7 +152,7 @@ main.map {
main.account form { main.account form {
max-width: 400px; max-width: 400px;
} }
#modal-content form button[type=submit] { #modal-content form button[type=submit], #modal-content .answers .button {
display: block; display: block;
width: 100%; width: 100%;
} }
@ -551,6 +551,14 @@ main.show-options #resultswrapper #route-options {
.location.location-form-value { .location.location-form-value {
margin: -10px -10px 5px -10px; margin: -10px -10px 5px -10px;
} }
.location-answers .location.location-form-value, .location-answers a {
margin-bottom: 5px;
color: $color-secondary;
display: block;
}
.location-answers {
margin-bottom: 10px;
}
.location .icon { .location .icon {
font-size: 36px; font-size: 36px;
position: absolute; position: absolute;

View file

@ -496,8 +496,6 @@ c3nav = {
var custom_location = typeof data.id !== 'number', var custom_location = typeof data.id !== 'number',
report_url = '/report/l/'+String(data.id)+'/'; report_url = '/report/l/'+String(data.id)+'/';
$location_details.find('.report').attr('href', report_url); $location_details.find('.report').attr('href', report_url);
$location_details.find('.report-issue').toggle(!custom_location);
$location_details.find('.report-missing').toggle(custom_location);
} else { } else {
$location_details.find('.report').hide(); $location_details.find('.report').hide();
} }
@ -1538,7 +1536,6 @@ c3nav = {
maxWidth: 500 maxWidth: 500
}, 'autoPanPaddingTopLeft', 'autoPanPaddingBottomRight')); }, 'autoPanPaddingTopLeft', 'autoPanPaddingBottomRight'));
var buttons = $('#location-popup-buttons').clone(); var buttons = $('#location-popup-buttons').clone();
buttons.find('.report-issue').remove();
buttons.find('.report').attr('href', '/report/l/' + String(data.id) + '/'); buttons.find('.report').attr('href', '/report/l/' + String(data.id) + '/');
newpopup.setLatLng(latlng).setContent(c3nav._build_location_html(data) + buttons.html()); newpopup.setLatLng(latlng).setContent(c3nav._build_location_html(data) + buttons.html());
c3nav._click_anywhere_popup = newpopup; c3nav._click_anywhere_popup = newpopup;
@ -1698,11 +1695,6 @@ c3nav = {
let buttons_html = ''; let buttons_html = '';
if (!c3nav.embed) { if (!c3nav.embed) {
let buttons = $('#location-popup-buttons').clone(); let buttons = $('#location-popup-buttons').clone();
if (typeof location.id == 'number') {
buttons.find('.report-missing').remove();
} else {
buttons.find('.report-issue').remove();
}
buttons.find('.report').attr('href', '/report/l/'+String(location.id)+'/'); buttons.find('.report').attr('href', '/report/l/'+String(location.id)+'/');
buttons_html = buttons.html(); buttons_html = buttons.html();
} }

View file

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

View file

@ -84,7 +84,7 @@
<div class="buttons"> <div class="buttons">
<a class="button button-clear report report-missing"> <a class="button button-clear report report-missing">
<i class="material-symbols">feedback</i> <i class="material-symbols">feedback</i>
{% trans 'Report missing location' %} {% trans 'Report issue' %}
</a> </a>
</div> </div>
</section> </section>
@ -110,7 +110,7 @@
</a> </a>
<a class="button button-clear report report-missing"> <a class="button button-clear report report-missing">
<i class="material-symbols">feedback</i> <i class="material-symbols">feedback</i>
{% trans 'Report missing location' %} {% trans 'Report issue' %}
</a> </a>
</div> </div>
</section> </section>
@ -206,10 +206,6 @@
<i class="material-symbols">feedback</i> <i class="material-symbols">feedback</i>
{% trans 'Report issue' %} {% trans 'Report issue' %}
</a> </a>
<a class="button button-clear report report-missing">
<i class="material-symbols">feedback</i>
{% trans 'Report missing location' %}
</a>
<a class="button button-clear editor" target="_blank"> <a class="button button-clear editor" target="_blank">
<i class="material-symbols">edit</i> <i class="material-symbols">edit</i>
{% trans 'Open in Editor' %} {% trans 'Open in Editor' %}

View file

@ -5,23 +5,15 @@
<main class="account"> <main class="account">
<h2>{% trans 'Report issue' %}</h2> <h2>{% trans 'Report issue' %}</h2>
{% include 'site/fragment_messages.html' %} {% include 'site/fragment_messages.html' %}
<form method="post" action="{{ request.path_info }}?{{ request.META.QUERY_STRING }}"> <form method="post" action="{{ request.path_info }}?{{ request.META.QUERY_STRING }}">
{% csrf_token %} {% csrf_token %}
{% include 'site/fragment_report_meta.html' %} {% include 'site/fragment_report_meta.html' %}
{% if help_text %}
<ul class="messages">
<li class="alert-info">{{ help_text }}</li>
</ul>
{% endif %}
{{ form.as_p }} {{ form.as_p }}
<p>
<input type="checkbox" required="required" style="margin-bottom: 0" />
<blink>
<strong>
{% blocktrans trimmed %}
I understand that if I report an issue related to an assembly,
all that will happen is that I owe each member on the c3nav team one bottle of mate.
I can avoid this by contacting the assembly team, which will actually be able to fix my issue.
{% endblocktrans %}
</strong>
</blink>
</p>
<button type="submit">{% trans 'Submit' %}</button> <button type="submit">{% trans 'Submit' %}</button>
</form> </form>
</main> </main>

View file

@ -0,0 +1,29 @@
{% extends 'site/base.html' %}
{% load i18n %}
{% block content %}
<main class="account">
<h2>{% trans 'Report issue' %}</h2>
{% include 'site/fragment_messages.html' %}
{% include 'site/fragment_report_meta.html' %}
<p>{{ question }}</p>
{% if locations %}
<div class="location-answers">
{% for location in locations %}
{% if location.url %}
<a href="{{ location.url }}">
{% endif %}
{% include 'site/fragment_location.html' with form_value=1 location=location.location replace_subtitle=location.replace_subtitle %}
{% if location.url %}
</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
<div class="answers">
{% for answer in answers %}
<p><a class="button" href="{{ answer.url }}">{{ answer.text }}</a></p>
{% endfor %}
</div>
</main>
{% endblock %}

View file

@ -6,7 +6,9 @@ from c3nav.site.converters import AtPositionConverter, CoordinatesConverter, IsE
from c3nav.site.views import (about_view, access_redeem_view, account_manage, account_view, api_secret_create, from c3nav.site.views import (about_view, access_redeem_view, account_manage, account_view, api_secret_create,
api_secret_list, change_password_view, choose_language, delete_account_view, login_view, api_secret_list, change_password_view, choose_language, delete_account_view, login_view,
logout_view, map_index, position_create, position_detail, position_list, position_set, logout_view, map_index, position_create, position_detail, position_list, position_set,
qr_code, register_view, report_create, report_detail, report_list) qr_code, register_view, report_create, report_detail, report_list,
report_start_coordinates, report_start_location, report_start_route, report_missing_check,
report_select_location, report_missing_choose)
register_converter(CoordinatesConverter, 'coords') register_converter(CoordinatesConverter, 'coords')
register_converter(AtPositionConverter, 'at_pos') register_converter(AtPositionConverter, 'at_pos')
@ -52,9 +54,16 @@ urlpatterns = [
path('reports/all/', report_list, {'filter': 'all'}, name='site.report_list'), path('reports/all/', report_list, {'filter': 'all'}, name='site.report_list'),
path('reports/<int:pk>/', report_detail, name='site.report_detail'), path('reports/<int:pk>/', report_detail, name='site.report_detail'),
path('reports/<int:pk>/<str:secret>/', report_detail, name='site.report_detail'), path('reports/<int:pk>/<str:secret>/', report_detail, name='site.report_detail'),
path('report/l/<coords:coordinates>/', report_create, name='site.report_create'), path('report/l/<coords:coordinates>/', report_start_coordinates, name='site.report_start'),
path('report/l/<int:location>/', report_create, name='site.report_create'), path('report/l/<coords:coordinates>/missing/', report_missing_check, name='site.report_missing_check'),
path('report/r/<str:origin>/<str:destination>/<str:options>/', report_create, name='site.report_create'), path('report/l/<coords:coordinates>/existing/', report_select_location, name='site.report_select_location'),
path('report/l/<coords:coordinates>/choose/', report_missing_choose, name='site.report_missing_choose'),
path('report/l/<int:location>/', report_start_location, name='site.report_start'),
path('report/r/<str:origin>/<str:destination>/<str:options>/', report_start_route, name='site.report_start'),
path('report/create/l/<coords:coordinates>/', report_create, name='site.report_create'),
path('report/create/l/<coords:coordinates>/<str:group>/', report_create, name='site.report_create'),
path('report/create/l/<int:location>/', report_create, name='site.report_create'),
path('report/create/r/<str:origin>/<str:destination>/<str:options>/', report_create, name='site.report_create'),
path('positions/', position_list, name='site.position_list'), path('positions/', position_list, name='site.position_list'),
path('positions/create/', position_create, name='site.position_create'), path('positions/create/', position_create, name='site.position_create'),
path('positions/<int:pk>/', position_detail, name='site.position_detail'), path('positions/<int:pk>/', position_detail, name='site.position_detail'),

View file

@ -30,7 +30,8 @@ from c3nav.api.models import Secret
from c3nav.mapdata.grid import grid from c3nav.mapdata.grid import grid
from c3nav.mapdata.models import Location, Source from c3nav.mapdata.models import Location, Source
from c3nav.mapdata.models.access import AccessPermission, AccessPermissionToken from c3nav.mapdata.models.access import AccessPermission, AccessPermissionToken
from c3nav.mapdata.models.locations import LocationRedirect, Position, SpecificLocation, get_position_secret from c3nav.mapdata.models.locations import LocationRedirect, Position, SpecificLocation, get_position_secret, \
LocationGroup
from c3nav.mapdata.models.report import Report, ReportUpdate from c3nav.mapdata.models.report import Report, ReportUpdate
from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request, from c3nav.mapdata.utils.locations import (get_location_by_id_for_request, get_location_by_slug_for_request,
levels_by_short_label_for_request) levels_by_short_label_for_request)
@ -453,15 +454,134 @@ def get_report_location_for_request(pk, request):
return location return location
@never_cache
def report_start_coordinates(request, coordinates):
return render(request, 'site/report_question.html', {
'question': _('What\'s wrong here?'),
'answers': [
{
'url': reverse('site.report_missing_check', kwargs={'coordinates': coordinates}),
'text': _('A location is missing'),
},
{
'url': reverse('site.report_select_location', kwargs={'coordinates': coordinates}),
'text': _('A location is there, but wrong'),
},
]
})
@never_cache
def report_missing_check(request, coordinates):
nearby = get_location_by_id_for_request(coordinates, request).nearby
if not nearby:
return redirect(reverse('site.report_missing_choose', kwargs={"coordinates": coordinates}))
return render(request, 'site/report_question.html', {
'question': _('Are you sure it\'s not one of these?'),
'locations': [
{
'location': get_location_by_id_for_request(location.id, request), # todo: correct subtitle w/o this
}
for location in nearby
],
'answers': [
{
'url': reverse('site.report_missing_choose', kwargs={"coordinates": coordinates}),
'text': _('Yeah, it\'s not in there'),
},
]
})
@never_cache
def report_select_location(request, coordinates):
location = get_location_by_id_for_request(coordinates, request)
nearby = list(location.nearby)
if location.space:
nearby.append(location.space)
if not nearby:
messages.error(request, _('There are no locations nearby.'))
return render(request, 'site/report_question.html', {})
return render(request, 'site/report_question.html', {
'question': _('Which one is it?'),
'locations': [
{
'url': reverse('site.report_create', kwargs={"location": location.id}),
'location': get_location_by_id_for_request(location.id, request), # todo: correct subtitle w/o this
}
for location in nearby
],
})
@never_cache
def report_missing_choose(request, coordinates):
groups = LocationGroup.qs_for_request(request).filter(can_report_missing__in=(
LocationGroup.CanReportMissing.SINGLE,
LocationGroup.CanReportMissing.REJECT,
))
if not groups.exists():
return redirect(reverse('site.report_create', kwargs={"coordinates": coordinates}))
return render(request, 'site/report_question.html', {
'question': _('Does one of these describe your missing location?'),
'locations': [
{
"url": reverse('site.report_create',
kwargs={"coordinates": coordinates, "group": group.get_slug()}),
"location": group,
"replace_subtitle": group.description
}
for group in groups
],
'answers': [
{
'url': reverse('site.report_create', kwargs={"coordinates": coordinates}),
'text': _('None of these fit'),
},
]
})
@never_cache
def report_start_location(request, location):
return redirect(reverse('site.report_create',
kwargs={"location": location}))
@never_cache
def report_start_route(request, origin, destination, options):
return redirect(reverse('site.report_create',
kwargs={"origin": origin, "destination": destination, "options": options}))
@never_cache @never_cache
@login_required(login_url='site.login') @login_required(login_url='site.login')
def report_create(request, coordinates=None, location=None, origin=None, destination=None, options=None): def report_create(request, coordinates=None, location=None, origin=None, destination=None, options=None, group=None):
report = Report() report = Report()
report.request = request report.request = request
form_kwargs = {}
help_text = None
if coordinates: if coordinates:
report.category = 'missing-location' report.category = 'missing-location'
report.coordinates_id = coordinates report.coordinates_id = coordinates
form_kwargs["request"] = request
if group:
group = get_location_by_slug_for_request(group, request)
if not isinstance(group, LocationGroup):
raise Http404
if group.can_report_missing == LocationGroup.CanReportMissing.REJECT:
messages.error(request, format_html(
'{}<br><br>{}',
_('We do not accept reports for this type of location.'),
group.report_help_text,
))
return render(request, 'site/report_question.html', {})
if group.can_report_missing != LocationGroup.CanReportMissing.SINGLE:
raise Http404
help_text = group.report_help_text
form_kwargs["group"] = group
try: try:
report.coordinates report.coordinates
except ObjectDoesNotExist: except ObjectDoesNotExist:
@ -491,7 +611,7 @@ def report_create(request, coordinates=None, location=None, origin=None, destina
report.options = options.serialize_string() report.options = options.serialize_string()
if request.method == 'POST': if request.method == 'POST':
form = report.form_cls(instance=report, data=request.POST) form = report.form_cls(instance=report, data=request.POST, **form_kwargs)
if form.is_valid(): if form.is_valid():
report = form.instance report = form.instance
if request.user.is_authenticated: if request.user.is_authenticated:
@ -508,12 +628,13 @@ def report_create(request, coordinates=None, location=None, origin=None, destina
messages.success(request, ' '.join(str(s) for s in success_messages)) messages.success(request, ' '.join(str(s) for s in success_messages))
return redirect(reverse('site.report_detail', kwargs=success_kwargs)) return redirect(reverse('site.report_detail', kwargs=success_kwargs))
else: else:
form = report.form_cls(instance=report) form = report.form_cls(instance=report, **form_kwargs)
return render(request, 'site/report_create.html', { return render(request, 'site/report_create.html', {
'report': report, 'report': report,
'options': options, 'options': options,
'form': form, 'form': form,
"help_text": help_text,
}) })