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

425 lines
17 KiB
Python
Raw Normal View History

2017-12-19 12:58:28 +01:00
import string
2018-12-16 19:47:39 +01:00
from datetime import datetime
from functools import wraps
2017-12-19 16:04:15 +01:00
from urllib.parse import urlencode
from django.conf import settings
2017-12-08 18:41:48 +01:00
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
2017-12-08 17:42:32 +01:00
from django.contrib.auth.models import User
2018-12-16 19:47:39 +01:00
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
2017-12-08 17:42:32 +01:00
from django.core.paginator import Paginator
from django.db import IntegrityError, transaction
2017-12-08 21:31:53 +01:00
from django.db.models import Prefetch
2017-12-08 18:41:48 +01:00
from django.shortcuts import get_object_or_404, redirect, render
2017-12-10 03:49:21 +01:00
from django.urls import reverse
from django.utils import timezone
2017-12-19 12:58:28 +01:00
from django.utils.crypto import get_random_string
2018-12-16 19:47:39 +01:00
from django.utils.timezone import make_aware
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView, ListView
2017-12-08 18:41:48 +01:00
2018-12-16 02:23:05 +01:00
from c3nav.control.forms import (AccessPermissionForm, AnnouncementForm, MapUpdateFilterForm, MapUpdateForm,
UserPermissionsForm, UserSpaceAccessForm)
from c3nav.control.models import UserPermissions, UserSpaceAccess
2018-12-16 02:23:05 +01:00
from c3nav.mapdata.models import MapUpdate
from c3nav.mapdata.models.access import AccessPermission, AccessPermissionToken, AccessRestriction
2018-12-16 02:23:05 +01:00
from c3nav.mapdata.tasks import process_map_updates
2023-10-02 15:35:08 +02:00
from c3nav.mesh.models import MeshNode
2017-12-10 15:23:53 +01:00
from c3nav.site.models import Announcement
2017-12-08 15:21:33 +01:00
class ControlPanelMixin(UserPassesTestMixin, LoginRequiredMixin):
login_url = 'site.login'
user_permission = None
def test_func(self):
if not self.user_permission:
return True
return getattr(self.request.user_permissions, self.user_permission)
def control_panel_view(func):
@wraps(func)
2017-12-08 17:42:32 +01:00
def wrapped_func(request, *args, **kwargs):
if not request.user_permissions.control_panel:
raise PermissionDenied
2017-12-08 17:42:32 +01:00
return func(request, *args, **kwargs)
return login_required(login_url='site.login')(wrapped_func)
class ControlPanelIndexView(ControlPanelMixin, TemplateView):
template_name = "control/index.html"
2017-12-08 17:42:32 +01:00
class UserListView(ControlPanelMixin, ListView):
model = User
paginate_by = 20
template_name = "control/users.html"
ordering = "id"
context_object_name = "users"
2017-12-08 17:42:32 +01:00
def get_queryset(self):
qs = super().get_queryset()
search = self.request.GET.get('s')
if search:
qs = qs.filter(username__icontains=search.strip())
return qs
2017-12-08 18:41:48 +01:00
@login_required(login_url='site.login')
2017-12-08 18:41:48 +01:00
@control_panel_view
def user_detail(request, user):
2017-12-08 21:31:53 +01:00
qs = User.objects.select_related(
'permissions',
).prefetch_related(
Prefetch('spaceaccesses', UserSpaceAccess.objects.select_related('space')),
Prefetch('accesspermissions', AccessPermission.objects.select_related('access_restriction', 'author'))
2017-12-08 21:31:53 +01:00
)
2017-12-08 18:41:48 +01:00
user = get_object_or_404(qs, pk=user)
2017-12-08 22:18:05 +01:00
if request.method == 'POST':
delete_access_permission = request.POST.get('delete_access_permission')
if delete_access_permission:
with transaction.atomic():
try:
permission = AccessPermission.objects.select_for_update().get(pk=delete_access_permission)
except AccessPermission.DoesNotExist:
messages.error(request, _('Unknown access permission.'))
else:
2017-12-19 12:29:50 +01:00
if request.user_permissions.grant_all_access or permission.author_id == request.user.pk:
permission.delete()
messages.success(request, _('Access Permission successfully deleted.'))
else:
messages.error(request, _('You cannot delete this Access Permission.'))
return redirect(request.path_info+'?restriction='+str(permission.pk)+'#access')
2017-12-08 22:18:05 +01:00
2017-12-19 12:58:28 +01:00
api_secret_action = request.POST.get('api_secret')
if (api_secret_action and (request.user_permissions.grant_permissions or
(request.user == user and user.permissions.api_secret))):
2017-12-19 12:58:28 +01:00
permissions = user.permissions
if api_secret_action == 'generate' and permissions.api_secret:
messages.error(request, _('This user already has an API secret.'))
return redirect(request.path_info)
if api_secret_action in ('delete', 'regenerate') and not permissions.api_secret:
messages.error(request, _('This user does not have an API secret.'))
return redirect(request.path_info)
with transaction.atomic():
if api_secret_action in ('generate', 'regenerate'):
api_secret = get_random_string(64, string.ascii_letters+string.digits)
permissions.api_secret = api_secret
permissions.save()
messages.success(request, _('The new API secret is: %s '
'be sure to note it down now, it won\'t be shown again.') % api_secret)
elif api_secret_action == 'delete':
permissions.api_secret = None
permissions.save()
messages.success(request, _('API secret successfully deleted!'))
return redirect(request.path_info)
2017-12-08 18:41:48 +01:00
ctx = {
'user': user,
}
# user permissions
try:
permissions = user.permissions
except AttributeError:
permissions = UserPermissions(user=user, initial=True)
2017-12-08 18:41:48 +01:00
ctx.update({
'user_permissions': tuple(
field.verbose_name for field in UserPermissions._meta.get_fields()
if not field.one_to_one and getattr(permissions, field.attname)
)
})
if request.user_permissions.grant_permissions:
if request.method == 'POST' and request.POST.get('submit_user_permissions'):
form = UserPermissionsForm(instance=permissions, data=request.POST)
if form.is_valid():
form.save()
messages.success(request, _('General permissions successfully updated.'))
return redirect(request.path_info)
else:
form = UserPermissionsForm(instance=permissions)
ctx.update({
'user_permissions_form': form
})
2017-12-08 21:31:53 +01:00
# access permissions
now = timezone.now()
restriction = request.GET.get('restriction')
if restriction and restriction.isdigit():
restriction = get_object_or_404(AccessRestriction, pk=restriction)
permissions = user.accesspermissions.filter(access_restriction=restriction).order_by('expire_date')
for permission in permissions:
permission.expired = permission.expire_date and permission.expire_date >= now
ctx.update({
'access_restriction': restriction,
'access_permissions': user.accesspermissions.filter(
access_restriction=restriction
).order_by('expire_date')
})
2017-12-08 21:31:53 +01:00
else:
if request.method == 'POST' and request.POST.get('submit_access_permissions'):
form = AccessPermissionForm(request=request, data=request.POST)
if form.is_valid():
form.get_token().redeem(user)
messages.success(request, _('Access permissions successfully granted.'))
return redirect(request.path_info)
else:
form = AccessPermissionForm(request=request)
access_permissions = {}
for permission in user.accesspermissions.all():
access_permissions.setdefault(permission.access_restriction_id, []).append(permission)
access_permissions = tuple(
{
'pk': pk,
'title': permissions[0].access_restriction.title,
'can_grant': any(item.can_grant for item in permissions),
'expire_date': set(item.expire_date for item in permissions),
} for pk, permissions in access_permissions.items()
)
for permission in access_permissions:
permission['expire_date'] = None if None in permission['expire_date'] else max(permission['expire_date'])
permission['expired'] = permission['expire_date'] and permission['expire_date'] >= now
access_permissions = tuple(sorted(
access_permissions,
key=lambda permission: (1, 0) if permission['expire_date'] is None else (0, permission['expire_date']),
reverse=True
))
ctx.update({
'access_permissions': access_permissions,
'access_permission_form': form
})
2017-12-08 21:31:53 +01:00
# space access
form = None
if request.user_permissions.grant_space_access:
if request.method == 'POST' and request.POST.get('submit_space_access'):
form = UserSpaceAccessForm(request=request, data=request.POST)
if form.is_valid():
instance = form.instance
instance.user = user
try:
instance.save()
except IntegrityError:
messages.error(request, _('User space access could not be granted because it already exists.'))
else:
messages.success(request, _('User space access successfully granted.'))
return redirect(request.path_info)
else:
form = UserSpaceAccessForm(request=request)
delete_space_access = request.POST.get('delete_space_access')
if delete_space_access:
with transaction.atomic():
try:
access = user.spaceaccesses.filter(pk=delete_space_access)
except AccessPermission.DoesNotExist:
messages.error(request, _('Unknown space access.'))
else:
if request.user_permissions.grant_space_access or user.pk == request.user.pk:
access.delete()
messages.success(request, _('Space access successfully deleted.'))
else:
messages.error(request, _('You cannot delete this space access.'))
return redirect(request.path_info)
space_accesses = None
if request.user_permissions.grant_space_access or user.pk == request.user.pk:
space_accesses = user.spaceaccesses.all()
ctx.update({
'space_accesses': space_accesses,
'space_accesses_form': form
})
2017-12-08 18:41:48 +01:00
return render(request, 'control/user.html', ctx)
2017-12-10 03:49:21 +01:00
@login_required(login_url='site.login')
2017-12-10 03:49:21 +01:00
@control_panel_view
def grant_access(request):
if request.method == 'POST' and request.POST.get('submit_access_permissions'):
form = AccessPermissionForm(request=request, data=request.POST)
if form.is_valid():
token = form.get_token()
token.save()
if settings.DEBUG and request.user_permissions.api_secret:
2017-12-19 15:46:43 +01:00
signed_data = form.get_signed_data()
2017-12-19 16:04:15 +01:00
print('/?'+urlencode({'access': signed_data}))
return redirect(reverse('control.access.qr', kwargs={'token': token.token}))
2017-12-10 03:49:21 +01:00
else:
form = AccessPermissionForm(request=request)
ctx = {
'access_permission_form': form
}
return render(request, 'control/access.html', ctx)
@login_required(login_url='site.login')
2017-12-10 03:49:21 +01:00
@control_panel_view
def grant_access_qr(request, token):
with transaction.atomic():
2017-12-18 14:54:45 +01:00
token = AccessPermissionToken.objects.select_for_update().get(token=token, author=request.user)
2017-12-10 03:49:21 +01:00
if token.redeemed:
messages.success(request, _('Access successfully granted.'))
2017-12-10 03:49:21 +01:00
token = None
elif request.method == 'POST' and request.POST.get('revoke'):
token.delete()
messages.success(request, _('Token successfully revoked.'))
return redirect('control.access')
2017-12-10 03:49:21 +01:00
elif not token.unlimited:
try:
latest = AccessPermissionToken.objects.filter(author=request.user).latest('valid_until')
except AccessPermissionToken.DoesNotExist:
token = None
else:
if latest.id != token.id:
token = None
if token is None:
2017-12-10 12:40:59 +01:00
messages.error(request, _('You can only display your most recently created token.'))
2017-12-10 03:49:21 +01:00
if token is None:
2017-12-10 14:13:20 +01:00
return redirect('control.access')
2017-12-10 03:49:21 +01:00
token.bump()
token.save()
2017-12-18 14:54:45 +01:00
url = reverse('site.access.redeem', kwargs={'token': str(token.token)})
2017-12-10 03:49:21 +01:00
return render(request, 'control/access_qr.html', {
2017-12-10 14:13:20 +01:00
'url': url,
'url_qr': reverse('site.qr', kwargs={'path': url}),
'url_absolute': request.build_absolute_uri(url),
2017-12-10 03:49:21 +01:00
})
2017-12-10 15:23:53 +01:00
@login_required(login_url='site.login')
2017-12-10 15:23:53 +01:00
@control_panel_view
def announcement_list(request):
if not request.user_permissions.manage_announcements:
raise PermissionDenied
2017-12-10 15:25:03 +01:00
announcements = Announcement.objects.order_by('-created')
2017-12-10 15:23:53 +01:00
if request.method == 'POST':
form = AnnouncementForm(data=request.POST)
if form.is_valid():
announcement = form.instance
2017-12-24 18:16:49 +01:00
announcement.author = request.user
2017-12-10 15:23:53 +01:00
announcement.save()
return redirect('control.announcements')
else:
form = AnnouncementForm()
return render(request, 'control/announcements.html', {
'form': form,
'announcements': announcements,
})
@login_required(login_url='site.login')
2017-12-10 15:23:53 +01:00
@control_panel_view
def announcement_detail(request, announcement):
if not request.user_permissions.manage_announcements:
raise PermissionDenied
announcement = get_object_or_404(Announcement, pk=announcement)
if request.method == 'POST':
form = AnnouncementForm(instance=announcement, data=request.POST)
if form.is_valid():
form.save()
return redirect('control.announcements')
else:
form = AnnouncementForm(instance=announcement)
return render(request, 'control/announcement.html', {
'form': form,
'announcement': announcement,
})
2018-12-16 02:23:05 +01:00
@login_required(login_url='site.login')
@control_panel_view
def map_updates(request):
if not request.user_permissions.manage_map_updates:
raise PermissionDenied
page = request.GET.get('page', 1)
if request.method == 'POST':
if 'create_map_update' in request.POST:
map_update_form = MapUpdateForm(data=request.POST)
if map_update_form.is_valid():
map_update = map_update_form.instance
map_update.type = 'control_panel'
map_update.user = request.user
map_update.save()
messages.success(request, _('Map update successfully created.'))
return redirect(request.path_info)
elif 'process_updates' in request.POST:
if settings.HAS_CELERY:
process_map_updates.delay()
messages.success(request, _('Map update processing successfully queued.'))
else:
messages.error(request, _('Map update processing was not be queued because celery is not configured.'))
return redirect(request.path_info)
filter_form = MapUpdateFilterForm(request.GET)
map_update_form = MapUpdateForm()
queryset = MapUpdate.objects.order_by('-datetime').select_related('user', 'changeset__author')
if request.GET.get('type', None):
queryset = queryset.filter(type=request.GET['type'])
if request.GET.get('geometries_changed', None):
if request.GET['geometries_changed'] in ('1', '0'):
queryset = queryset.filter(geometries_changed=request.GET['geometries_changed'] == '1')
if request.GET.get('processed', None):
if request.GET['processed'] in ('1', '0'):
queryset = queryset.filter(processed=request.GET['processed'] == '1')
if request.GET.get('user_id', None):
if request.GET['user_id'].isdigit():
queryset = queryset.filter(user_id=request.GET['user_id'])
paginator = Paginator(queryset, 20)
users = paginator.page(page)
2018-12-16 19:47:39 +01:00
last_processed, last_processed_success = cache.get('mapdata:last_process_updates_run', (None, None))
if last_processed:
2018-12-16 19:55:17 +01:00
last_processed = make_aware(datetime.fromtimestamp(last_processed))
2018-12-16 19:47:39 +01:00
2018-12-21 17:43:30 +01:00
last_processed_start = cache.get('mapdata:last_process_updates_start', None)
if last_processed_start:
last_processed_start = make_aware(datetime.fromtimestamp(last_processed_start))
2018-12-16 02:23:05 +01:00
return render(request, 'control/map_updates.html', {
2018-12-16 19:47:39 +01:00
'last_processed': last_processed,
2018-12-21 17:43:30 +01:00
'last_processed_start': last_processed_start,
2018-12-16 19:47:39 +01:00
'last_processed_success': last_processed_success,
2018-12-16 02:23:05 +01:00
'auto_process_updates': settings.AUTO_PROCESS_UPDATES,
'map_update_form': map_update_form,
'filter_form': filter_form,
'updates': users,
})
2023-10-02 15:35:08 +02:00
class MeshNodeListView(ControlPanelMixin, ListView):
model = MeshNode
template_name = "control/mesh_nodes.html"
ordering = "address"
context_object_name = "nodes"