manage map updates in control panel

This commit is contained in:
Laura Klünder 2018-12-16 02:23:05 +01:00
parent 84fd2b0011
commit e170e128bc
11 changed files with 258 additions and 6 deletions

View file

@ -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', )

View file

@ -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'),
),
]

View file

@ -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'))

View file

@ -21,6 +21,9 @@
{% if request.user_permissions.manage_announcements %}
<a href="{% url 'control.announcements' %}">{% trans 'Announcements' %}</a> &middot;
{% endif %}
{% if request.user_permissions.manage_map_updates %}
<a href="{% url 'control.map_updates' %}">{% trans 'Map Updates' %}</a> &middot;
{% endif %}
<a href="{% url 'control.users.detail' user=request.user.pk %}">{{ request.user.username }}</a>
</p>
</nav>

View file

@ -0,0 +1,96 @@
{% extends 'control/base.html' %}
{% load i18n %}
{% block heading %}{% trans 'Map updates' %}{% endblock %}
{% block subcontent %}
<div class="columns">
<div>
<h4>{% trans 'Create map update' %}</h4>
<form method="post">
{% csrf_token %}
<label style="font-weight:normal;">
{{ map_update_form.geometries_changed }} {{ map_update_form.geometries_changed.label }}
</label>
<button type="submit" name="create_map_update" value="1">{% trans 'Create map update' %}</button>
</form>
</div>
<div>
<h4>{% trans 'Process updates' %}</h4>
<form method="post">
{% csrf_token %}
{% if auto_process_updates %}
<p class="green">{% trans 'Map updates are currently processed automatically.' %}</p>
{% else %}
<p>{% trans 'Map updates are currently not processed automatically.' %}</p>
{% endif %}
<button type="submit" name="process_updates" value="1">{% trans 'Process map updates now' %}</button>
</form>
</div>
</div>
<hr>
<h4>{% trans 'List of map updates' %}</h4>
<form class="filter-form">
{% for field in filter_form %}
{{ field }}
{% endfor %}
<button type="submit">{% trans 'Filter' %}</button>
</form>
{% include 'control/fragment_pagination.html' with objects=updates %}
<table>
<tr>
<th>{% trans 'ID' %}</th>
<th>{% trans 'Date' %}</th>
<th>{% trans 'Reason' %}</th>
<th>{% trans 'Geometries' %}</th>
<th>{% trans 'Processed' %}</th>
</tr>
{% for update in updates %}
<tr>
<td>{{ update.id }}</td>
<td>{{ update.datetime|date:"SHORT_DATETIME_FORMAT" }}</td>{% comment %}<td><a href="{% url 'control.users.detail' user=user.pk %}">{{ user.username }}</a></td>{% endcomment %}
<td>
{% 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 <a href="{{ user_url }}">{{ user_name }}</a>
{% 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 <a href="{{ user_url }}">{{ user_name }}</a>
{% 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 <a href="{{ author_url }}">{{ author_name }}</a> applied by <a href="{{ user_url }}">{{ user_name }}</a>
{% endblocktrans %}
{% else %}
{{ update.type }}
{% endif %}
</td>
<td>
{% if update.geometries_changed %}
<strong class="green">{% trans 'Yes' %}</strong>
{% else %}
{% trans 'No' %}
{% endif %}
</td>
<td>
{% if update.processed %}
{% trans 'Yes' %}
{% else %}
<strong class="red">{% trans 'No' %}</strong>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% include 'control/fragment_pagination.html' with objects=updates %}
{% endblock %}

View file

@ -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<token>[^/]+)$', grant_access_qr, name='control.access.qr'),
url(r'^announcements/$', announcement_list, name='control.announcements'),
url(r'^announcements/(?P<announcement>\d+)/$', announcement_detail, name='control.announcements.detail'),
url(r'^mapupdates/$', map_updates, name='control.map_updates'),
url(r'^$', main_index, name='control.index'),
]

View file

@ -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,
})

View file

@ -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),
),
]

View file

@ -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()
)

View file

@ -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')

View file

@ -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;
}