From e170e128bce9ecb2531108f69a8d1d6b42bb52ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Sun, 16 Dec 2018 02:23:05 +0100 Subject: [PATCH] manage map updates in control panel --- src/c3nav/control/forms.py | 30 +++++- ...0007_userpermissions_manage_map_updates.py | 18 ++++ src/c3nav/control/models.py | 1 + src/c3nav/control/templates/control/base.html | 3 + .../templates/control/map_updates.html | 96 +++++++++++++++++++ src/c3nav/control/urls.py | 3 +- src/c3nav/control/views.py | 58 ++++++++++- .../migrations/0004_mapupdate_types.py | 18 ++++ src/c3nav/mapdata/models/update.py | 10 +- src/c3nav/settings.py | 1 + src/c3nav/site/static/site/css/c3nav.scss | 26 +++++ 11 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 src/c3nav/control/migrations/0007_userpermissions_manage_map_updates.py create mode 100644 src/c3nav/control/templates/control/map_updates.html create mode 100644 src/c3nav/mapdata/migrations/0004_mapupdate_types.py diff --git a/src/c3nav/control/forms.py b/src/c3nav/control/forms.py index 7acfb3dd..d0730def 100644 --- a/src/c3nav/control/forms.py +++ b/src/c3nav/control/forms.py @@ -9,14 +9,14 @@ from itertools import chain import pytz from django.contrib.auth.models import User from django.db.models import Prefetch -from django.forms import ChoiceField, Form, ModelForm, Select +from django.forms import ChoiceField, Form, IntegerField, ModelForm, Select from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext_lazy from c3nav.control.models import UserPermissions, UserSpaceAccess from c3nav.mapdata.forms import I18nModelFormMixin -from c3nav.mapdata.models import Space +from c3nav.mapdata.models import MapUpdate, Space from c3nav.mapdata.models.access import (AccessPermission, AccessPermissionToken, AccessPermissionTokenItem, AccessRestriction, AccessRestrictionGroup) from c3nav.site.models import Announcement @@ -265,3 +265,29 @@ class AnnouncementForm(I18nModelFormMixin, ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['active_until'].initial = timezone.now() + + +class MapUpdateFilterForm(Form): + type = ChoiceField( + choices=(('', _('any type')), ) + MapUpdate.TYPES, + required=False + ) + geometries_changed = ChoiceField( + choices=(('', _('any')), ('1', _('geometries changed')), ('0', _('no geometries changed'))), + required=False + ) + processed = ChoiceField( + choices=(('', _('any')), ('1', _('processed')), ('0', _('not processed'))), + required=False + ) + user_id = IntegerField(min_value=1, required=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['user_id'].widget.attrs['placeholder'] = _('user id') + + +class MapUpdateForm(ModelForm): + class Meta: + model = MapUpdate + fields = ('geometries_changed', ) diff --git a/src/c3nav/control/migrations/0007_userpermissions_manage_map_updates.py b/src/c3nav/control/migrations/0007_userpermissions_manage_map_updates.py new file mode 100644 index 00000000..0f37338b --- /dev/null +++ b/src/c3nav/control/migrations/0007_userpermissions_manage_map_updates.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2018-12-16 00:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('control', '0006_user_space_access'), + ] + + operations = [ + migrations.AddField( + model_name='userpermissions', + name='manage_map_updates', + field=models.BooleanField(default=False, verbose_name='manage map updates'), + ), + ] diff --git a/src/c3nav/control/models.py b/src/c3nav/control/models.py index 8701c2a2..ec68fe83 100644 --- a/src/c3nav/control/models.py +++ b/src/c3nav/control/models.py @@ -22,6 +22,7 @@ class UserPermissions(models.Model): max_changeset_changes = models.PositiveSmallIntegerField(default=10, verbose_name=_('max changes per changeset')) editor_access = models.BooleanField(default=False, verbose_name=_('can always access editor')) base_mapdata_access = models.BooleanField(default=False, verbose_name=_('can always access base map data')) + manage_map_updates = models.BooleanField(default=False, verbose_name=_('manage map updates')) control_panel = models.BooleanField(default=False, verbose_name=_('can access control panel')) grant_permissions = models.BooleanField(default=False, verbose_name=_('can grant control permissions')) diff --git a/src/c3nav/control/templates/control/base.html b/src/c3nav/control/templates/control/base.html index 19021b1e..605a1fdb 100644 --- a/src/c3nav/control/templates/control/base.html +++ b/src/c3nav/control/templates/control/base.html @@ -21,6 +21,9 @@ {% if request.user_permissions.manage_announcements %} {% trans 'Announcements' %} · {% endif %} + {% if request.user_permissions.manage_map_updates %} + {% trans 'Map Updates' %} · + {% endif %} {{ request.user.username }}

diff --git a/src/c3nav/control/templates/control/map_updates.html b/src/c3nav/control/templates/control/map_updates.html new file mode 100644 index 00000000..7030969c --- /dev/null +++ b/src/c3nav/control/templates/control/map_updates.html @@ -0,0 +1,96 @@ +{% extends 'control/base.html' %} +{% load i18n %} + +{% block heading %}{% trans 'Map updates' %}{% endblock %} + +{% block subcontent %} +
+
+

{% trans 'Create map update' %}

+
+ {% csrf_token %} + + +
+
+
+

{% trans 'Process updates' %}

+
+ {% csrf_token %} + {% if auto_process_updates %} +

{% trans 'Map updates are currently processed automatically.' %}

+ {% else %} +

{% trans 'Map updates are currently not processed automatically.' %}

+ {% endif %} + +
+
+
+
+

{% trans 'List of map updates' %}

+
+ {% for field in filter_form %} + {{ field }} + {% endfor %} + +
+ + {% include 'control/fragment_pagination.html' with objects=updates %} + + + + + + + + + + {% for update in updates %} + + + {% comment %}{% endcomment %} + + + + + {% endfor %} +
{% trans 'ID' %}{% trans 'Date' %}{% trans 'Reason' %}{% trans 'Geometries' %}{% trans 'Processed' %}
{{ update.id }}{{ update.datetime|date:"SHORT_DATETIME_FORMAT" }}{{ user.username }} + {% if update.type == 'management' %} + manage.py clearmapcache + {% elif update.type == 'control_panel' %} + {% url 'control.users.detail' user=update.user.pk as user_url %} + {% blocktrans with user_name=update.user.username %} + via control panel by {{ user_name }} + {% endblocktrans %} + {% elif update.type == 'direct_edit' %} + {% url 'control.users.detail' user=update.user.pk as user_url %} + {% blocktrans with user_name=update.user.username %} + direct edit by {{ user_name }} + {% endblocktrans %} + {% elif update.type == 'changeset' %} + {% url 'control.users.detail' user=update.user.pk as user_url %} + {% url 'control.users.detail' user=update.changeset.author.pk as author_url %} + {% blocktrans with changeset_id=update.changeset.pk user_name=update.user.username author_name=update.changeset.author.username %} + Changeset #{{ changeset_id }} by {{ author_name }} applied by {{ user_name }} + {% endblocktrans %} + {% else %} + {{ update.type }} + {% endif %} + + {% if update.geometries_changed %} + {% trans 'Yes' %} + {% else %} + {% trans 'No' %} + {% endif %} + + {% if update.processed %} + {% trans 'Yes' %} + {% else %} + {% trans 'No' %} + {% endif %} +
+ + {% include 'control/fragment_pagination.html' with objects=updates %} +{% endblock %} diff --git a/src/c3nav/control/urls.py b/src/c3nav/control/urls.py index f6a220d9..8ec281f9 100644 --- a/src/c3nav/control/urls.py +++ b/src/c3nav/control/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from c3nav.control.views import (announcement_detail, announcement_list, grant_access, grant_access_qr, main_index, - user_detail, user_list) + map_updates, user_detail, user_list) urlpatterns = [ url(r'^users/$', user_list, name='control.users'), @@ -10,5 +10,6 @@ urlpatterns = [ url(r'^access/(?P[^/]+)$', grant_access_qr, name='control.access.qr'), url(r'^announcements/$', announcement_list, name='control.announcements'), url(r'^announcements/(?P\d+)/$', announcement_detail, name='control.announcements.detail'), + url(r'^mapupdates/$', map_updates, name='control.map_updates'), url(r'^$', main_index, name='control.index'), ] diff --git a/src/c3nav/control/views.py b/src/c3nav/control/views.py index 2cb19bb5..f13984c7 100644 --- a/src/c3nav/control/views.py +++ b/src/c3nav/control/views.py @@ -16,9 +16,12 @@ from django.utils import timezone from django.utils.crypto import get_random_string from django.utils.translation import ugettext_lazy as _ -from c3nav.control.forms import AccessPermissionForm, AnnouncementForm, UserPermissionsForm, UserSpaceAccessForm +from c3nav.control.forms import (AccessPermissionForm, AnnouncementForm, MapUpdateFilterForm, MapUpdateForm, + UserPermissionsForm, UserSpaceAccessForm) from c3nav.control.models import UserPermissions, UserSpaceAccess +from c3nav.mapdata.models import MapUpdate from c3nav.mapdata.models.access import AccessPermission, AccessPermissionToken, AccessRestriction +from c3nav.mapdata.tasks import process_map_updates from c3nav.site.models import Announcement @@ -337,3 +340,56 @@ def announcement_detail(request, announcement): 'form': form, 'announcement': announcement, }) + + +@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) + + return render(request, 'control/map_updates.html', { + 'auto_process_updates': settings.AUTO_PROCESS_UPDATES, + 'map_update_form': map_update_form, + 'filter_form': filter_form, + 'updates': users, + }) diff --git a/src/c3nav/mapdata/migrations/0004_mapupdate_types.py b/src/c3nav/mapdata/migrations/0004_mapupdate_types.py new file mode 100644 index 00000000..0de00778 --- /dev/null +++ b/src/c3nav/mapdata/migrations/0004_mapupdate_types.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.4 on 2018-12-16 01:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mapdata', '0003_column_access_restriction'), + ] + + operations = [ + migrations.AlterField( + model_name='mapupdate', + name='type', + field=models.CharField(choices=[('changeset', 'changeset applied'), ('direct_edit', 'direct edit'), ('control_panel', 'via control panel'), ('management', 'manage.py clearmapcache')], max_length=32), + ), + ] diff --git a/src/c3nav/mapdata/models/update.py b/src/c3nav/mapdata/models/update.py index 6906a0d6..de1adca5 100644 --- a/src/c3nav/mapdata/models/update.py +++ b/src/c3nav/mapdata/models/update.py @@ -18,9 +18,15 @@ class MapUpdate(models.Model): """ A map update. created whenever mapdata is changed. """ + TYPES = ( + ('changeset', _('changeset applied')), + ('direct_edit', _('direct edit')), + ('control_panel', _('via control panel')), + ('management', _('manage.py clearmapcache')), + ) datetime = models.DateTimeField(auto_now_add=True, db_index=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.PROTECT) - type = models.CharField(max_length=32) + type = models.CharField(max_length=32, choices=TYPES) processed = models.BooleanField(default=False) geometries_changed = models.BooleanField() @@ -202,7 +208,7 @@ class MapUpdate(models.Model): transaction.on_commit( lambda: cache.set('mapdata:last_update', self.to_tuple, None) ) - if settings.HAS_CELERY: + if settings.HAS_CELERY and settings.AUTO_PROCESS_UPDATES: transaction.on_commit( lambda: process_map_updates.delay() ) diff --git a/src/c3nav/settings.py b/src/c3nav/settings.py index 8493eb11..d53c34ad 100644 --- a/src/c3nav/settings.py +++ b/src/c3nav/settings.py @@ -46,6 +46,7 @@ if not os.path.exists(CACHE_ROOT): PUBLIC_EDITOR = config.getboolean('c3nav', 'editor', fallback=True) PUBLIC_BASE_MAPDATA = config.getboolean('c3nav', 'public_base_mapdata', fallback=False) +AUTO_PROCESS_UPDATES = config.getboolean('c3nav', 'auto_process_updates', fallback=True) if config.has_option('django', 'secret'): SECRET_KEY = config.get('django', 'secret') diff --git a/src/c3nav/site/static/site/css/c3nav.scss b/src/c3nav/site/static/site/css/c3nav.scss index bc7997b0..91cfbaa3 100644 --- a/src/c3nav/site/static/site/css/c3nav.scss +++ b/src/c3nav/site/static/site/css/c3nav.scss @@ -920,6 +920,32 @@ ul.messages li.alert-danger { vertical-align: top; } +.filter-form { + margin-bottom: 0; +} +.filter-form input, .filter-form select { + width: auto; + vertical-align: top; +} +.filter-form input[type=number] { + width: 100px; +} + +.columns { + display:flex; + width: 100%; +} +.columns > div { + padding-right: 10px; + flex-grow: 1; +} +.columns > div > h4:first-child { + margin-top: 0; +} +.columns form { + margin-bottom: 0; +} + main.control p { margin-bottom: 1.0rem; }