team-3/src/c3nav/site/views.py

440 lines
15 KiB
Python
Raw Normal View History

import json
2018-12-09 00:11:01 +01:00
from itertools import chain
2017-11-28 16:17:08 +01:00
from typing import Optional
2016-12-15 17:30:55 +01:00
2016-12-22 02:40:12 +01:00
import qrcode
from django.conf import settings
from django.contrib import messages
2017-12-07 13:12:56 +01:00
from django.contrib.auth import login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm, UserCreationForm
2017-12-19 15:46:43 +01:00
from django.contrib.auth.views import redirect_to_login
2019-12-24 17:28:41 +01:00
from django.core.exceptions import ObjectDoesNotExist, SuspiciousOperation
2017-11-30 13:31:45 +01:00
from django.core.serializers.json import DjangoJSONEncoder
2017-12-10 14:13:20 +01:00
from django.db import transaction
2019-12-24 17:28:41 +01:00
from django.http import Http404, HttpResponse, HttpResponseBadRequest
2017-12-18 19:14:26 +01:00
from django.middleware import csrf
2019-12-24 17:28:41 +01:00
from django.shortcuts import get_object_or_404, redirect, render
2017-12-07 13:12:56 +01:00
from django.urls import reverse
2017-12-10 14:13:20 +01:00
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
2017-12-10 14:13:20 +01:00
from django.utils.translation import ungettext_lazy
2017-12-07 13:12:56 +01:00
from django.views.decorators.cache import cache_control, never_cache
2017-12-07 00:52:13 +01:00
from django.views.decorators.clickjacking import xframe_options_exempt
2017-11-30 18:23:47 +01:00
from django.views.decorators.http import etag
2017-12-19 15:46:43 +01:00
from c3nav.control.forms import AccessPermissionForm, SignedPermissionDataError
2018-12-12 01:42:44 +01:00
from c3nav.mapdata.grid import grid
from c3nav.mapdata.models import Location, Source
2017-12-10 14:13:20 +01:00
from c3nav.mapdata.models.access import AccessPermissionToken
from c3nav.mapdata.models.locations import LocationRedirect, SpecificLocation
2019-12-24 17:28:41 +01:00
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)
2018-09-19 19:08:47 +02:00
from c3nav.mapdata.utils.user import can_access_editor, get_user_data
from c3nav.mapdata.views import set_tile_access_cookie
2019-12-24 17:28:41 +01:00
from c3nav.routing.models import RouteOptions
from c3nav.site.models import Announcement, SiteUpdate
2016-12-15 17:30:55 +01:00
2016-12-22 02:40:12 +01:00
def check_location(location: Optional[str], request) -> Optional[SpecificLocation]:
if location is None:
return None
location = get_location_by_slug_for_request(location, request)
if location is None:
return None
if isinstance(location, LocationRedirect):
location: Location = location.target
if location is None:
return None
if not location.can_search:
location = None
return location
2017-12-16 19:33:13 +01:00
def map_index(request, mode=None, slug=None, slug2=None, details=None, options=None,
2017-12-07 00:52:13 +01:00
level=None, x=None, y=None, zoom=None, embed=None):
2017-12-19 15:46:43 +01:00
# check for access token
access_signed_data = request.GET.get('access')
if access_signed_data:
try:
token = AccessPermissionForm.load_signed_data(access_signed_data)
except SignedPermissionDataError as e:
return HttpResponse(str(e).encode(), content_type='text/plain', status=400)
num_restrictions = len(token.restrictions)
with transaction.atomic():
token.save()
if not request.user.is_authenticated:
messages.info(request, _('You need to log in to unlock areas.'))
request.session['redeem_token_on_login'] = str(token.token)
token.redeem()
2017-12-19 16:04:15 +01:00
return redirect_to_login(request.path_info, 'site.login')
2017-12-19 15:46:43 +01:00
token.redeem(request.user)
token.save()
messages.success(request, ungettext_lazy('Area successfully unlocked.',
'Areas successfully unlocked.', num_restrictions))
return redirect('site.index')
origin = None
destination = None
routing = False
if slug2 is not None:
routing = True
origin = check_location(slug, request)
destination = check_location(slug2, request)
else:
routing = (mode and mode != 'l')
if mode == 'o':
origin = check_location(slug, request)
else:
destination = check_location(slug, request)
state = {
'routing': routing,
'origin': (origin.serialize(detailed=False, simple_geometry=True, geometry=False)
if origin else None),
'destination': (destination.serialize(detailed=False, simple_geometry=True, geometry=False)
if destination else None),
'sidebar': routing or destination is not None,
2017-11-22 18:02:11 +01:00
'details': True if details else False,
2017-12-16 19:33:13 +01:00
'options': True if options else False,
}
2017-11-28 20:24:39 +01:00
levels = levels_by_short_label_for_request(request)
level = levels.get(level, None) if level else None
if level is not None:
state.update({
2017-11-28 20:44:15 +01:00
'level': level.pk,
'center': (float(x), float(y)),
'zoom': float(zoom),
})
initial_bounds = settings.INITIAL_BOUNDS
if not initial_bounds:
2018-12-09 00:11:01 +01:00
initial_bounds = tuple(chain(*Source.max_bounds()))
ctx = {
2017-10-25 12:15:19 +02:00
'bounds': json.dumps(Source.max_bounds(), separators=(',', ':')),
2017-11-28 20:38:37 +01:00
'levels': json.dumps(tuple((level.pk, level.short_label) for level in levels.values()), separators=(',', ':')),
2017-11-30 13:31:45 +01:00
'state': json.dumps(state, separators=(',', ':'), cls=DjangoJSONEncoder),
'tile_cache_server': settings.TILE_CACHE_SERVER,
2017-12-19 20:03:12 +01:00
'initial_level': settings.INITIAL_LEVEL,
2018-12-23 22:40:22 +01:00
'primary_color': settings.PRIMARY_COLOR,
'initial_bounds': json.dumps(initial_bounds, separators=(',', ':')) if initial_bounds else None,
'last_site_update': json.dumps(SiteUpdate.last_update()),
'ssids': json.dumps(settings.WIFI_SSIDS, separators=(',', ':')) if settings.WIFI_SSIDS else None,
2018-09-19 19:08:47 +02:00
'editor': can_access_editor(request),
2017-12-07 00:52:13 +01:00
'embed': bool(embed),
}
2017-12-10 15:40:28 +01:00
2018-12-12 01:42:44 +01:00
if grid.enabled:
ctx['grid'] = json.dumps({
'rows': grid.rows,
'cols': grid.cols,
'invert_x': grid.invert_x,
'invert_y': grid.invert_y,
}, separators=(',', ':'), cls=DjangoJSONEncoder)
2017-12-18 19:14:26 +01:00
csrf.get_token(request)
2017-12-26 15:06:47 +01:00
if not embed:
announcement = Announcement.get_current()
if announcement:
messages.info(request, announcement.text)
2017-12-10 15:40:28 +01:00
response = render(request, 'site/map.html', ctx)
set_tile_access_cookie(request, response)
2017-12-07 00:52:13 +01:00
if embed:
xframe_options_exempt(lambda: response)()
return response
2017-11-30 18:23:47 +01:00
def qr_code_etag(request, path):
return '1'
@etag(qr_code_etag)
@cache_control(max_age=3600)
def qr_code(request, path):
data = (request.build_absolute_uri('/'+path) +
('?'+request.META['QUERY_STRING'] if request.META['QUERY_STRING'] else ''))
if len(data) > 256:
return HttpResponseBadRequest()
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=2,
2017-11-30 18:23:47 +01:00
)
qr.add_data(data)
qr.make(fit=True)
response = HttpResponse(content_type='image/png')
qr.make_image().save(response, 'PNG')
return response
2017-12-07 13:12:56 +01:00
def close_response(request):
ajax = request.is_ajax() or 'ajax' in request.GET
if ajax:
return HttpResponse(json.dumps(get_user_data(request), cls=DjangoJSONEncoder).encode(),
content_type='text/plain')
redirect_path = request.GET['next'] if request.GET.get('next', '').startswith('/') else reverse('site.index')
return redirect(redirect_path)
2017-12-10 14:46:41 +01:00
def redeem_token_after_login(request):
2017-12-11 01:54:26 +01:00
token = request.session.pop('redeem_token_on_login', None)
2017-12-10 14:46:41 +01:00
if not token:
return
try:
2017-12-18 14:54:45 +01:00
token = AccessPermissionToken.objects.get(token=token)
2017-12-10 14:46:41 +01:00
except AccessPermissionToken.DoesNotExist:
return
try:
token.redeem(request.user)
except AccessPermissionToken.RedeemError:
messages.error(request, _('Areas could not be unlocked because the token has expired.'))
return
messages.success(request, token.redeem_success_message)
2017-12-07 13:12:56 +01:00
@never_cache
def login_view(request):
if request.user.is_authenticated:
return close_response(request)
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
login(request, form.user_cache)
2017-12-10 14:46:41 +01:00
redeem_token_after_login(request)
2017-12-07 13:12:56 +01:00
return close_response(request)
else:
form = AuthenticationForm(request)
ctx = {
'title': _('Log in'),
'form': form,
}
if settings.USER_REGISTRATION:
ctx.update({
'bottom_link_url': reverse('site.register'),
'bottom_link_text': _('Create new account')
})
return render(request, 'site/account_form.html', ctx)
2017-12-07 13:12:56 +01:00
@never_cache
def logout_view(request):
logout(request)
return close_response(request)
2017-12-07 16:46:12 +01:00
@never_cache
def register_view(request):
if not settings.USER_REGISTRATION:
return HttpResponse(_('account creation is currently disabled.'), content_type='text/plain', status=403)
2017-12-07 16:46:12 +01:00
if request.user.is_authenticated:
return close_response(request)
if request.method == 'POST':
form = UserCreationForm(data=request.POST)
if form.is_valid():
user = form.save()
login(request, user)
2017-12-10 14:46:41 +01:00
redeem_token_after_login(request)
2017-12-07 16:46:12 +01:00
return close_response(request)
else:
form = UserCreationForm()
form.fields['username'].max_length = 20
2017-12-07 16:46:12 +01:00
for field in form.fields.values():
field.help_text = None
return render(request, 'site/account_form.html', {
2017-12-10 14:25:25 +01:00
'title': _('Create new account'),
'back_url': reverse('site.login'),
'form': form
})
2017-12-07 16:46:12 +01:00
@never_cache
@login_required(login_url='site.login')
def change_password_view(request):
if request.method == 'POST':
form = PasswordChangeForm(user=request.user, data=request.POST)
if form.is_valid():
form.save()
login(request, request.user)
messages.success(request, _('Password successfully changed.'))
return redirect('site.account')
else:
form = PasswordChangeForm(user=request.user)
for field in form.fields.values():
field.help_text = None
return render(request, 'site/account_form.html', {
'title': _('Change password'),
'back_url': reverse('site.account'),
'form': form
})
2017-12-07 13:12:56 +01:00
@never_cache
@login_required(login_url='site.login')
def account_view(request):
2017-12-07 13:19:15 +01:00
return render(request, 'site/account.html', {})
2017-12-10 14:13:20 +01:00
@never_cache
def access_redeem_view(request, token):
with transaction.atomic():
try:
2017-12-18 14:54:45 +01:00
token = AccessPermissionToken.objects.select_for_update().get(token=token, redeemed=False,
2017-12-10 14:13:20 +01:00
valid_until__gte=timezone.now())
except AccessPermissionToken.DoesNotExist:
messages.error(request, _('This token does not exist or was already redeemed.'))
return redirect('site.index')
num_restrictions = len(token.restrictions)
if request.method == 'POST':
if not request.user.is_authenticated:
messages.info(request, _('You need to log in to unlock areas.'))
2017-12-18 14:54:45 +01:00
request.session['redeem_token_on_login'] = str(token.token)
2017-12-18 15:01:17 +01:00
token.redeem()
2017-12-19 16:04:15 +01:00
return redirect('site.login')
2017-12-10 14:13:20 +01:00
2017-12-18 15:01:17 +01:00
token.redeem(request.user)
2017-12-10 14:13:20 +01:00
token.save()
messages.success(request, ungettext_lazy('Area successfully unlocked.',
'Areas successfully unlocked.', num_restrictions))
return redirect('site.index')
return render(request, 'site/confirm.html', {
'title': ungettext_lazy('Unlock area', 'Unlock areas', num_restrictions),
'texts': (ungettext_lazy('You have been invited to unlock the following area:',
'You have been invited to unlock the following areas:',
num_restrictions),
', '.join(str(restriction.title) for restriction in token.restrictions)),
})
2017-12-10 16:22:26 +01:00
def choose_language(request):
return render(request, 'site/language.html', {})
2018-12-17 19:49:17 +01:00
@never_cache
def about_view(request):
return render(request, 'site/about.html', {
'address': settings.IMPRINT_ADDRESS,
'patrons': settings.IMPRINT_PATRONS,
2018-12-17 20:31:12 +01:00
'team': settings.IMPRINT_TEAM,
'hosting': settings.IMPRINT_HOSTING,
2018-12-17 19:49:17 +01:00
})
2019-12-24 17:28:41 +01:00
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
2019-12-24 17:28:41 +01:00
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,
})