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' %}
+
+
+
+
{% trans 'Process updates' %}
+
+
+
+
+ {% trans 'List of map updates' %}
+
+
+ {% include 'control/fragment_pagination.html' with objects=updates %}
+
+
+
+ {% trans 'ID' %} |
+ {% trans 'Date' %} |
+ {% trans 'Reason' %} |
+ {% trans 'Geometries' %} |
+ {% trans 'Processed' %} |
+
+ {% for update in updates %}
+
+ {{ update.id }} |
+ {{ update.datetime|date:"SHORT_DATETIME_FORMAT" }} | {% comment %}{{ user.username }} | {% endcomment %}
+
+ {% 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 %}
+ |
+
+ {% endfor %}
+
+
+ {% 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;
}