manage map updates in control panel
This commit is contained in:
parent
84fd2b0011
commit
e170e128bc
11 changed files with 258 additions and 6 deletions
|
@ -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', )
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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'))
|
||||
|
|
|
@ -21,6 +21,9 @@
|
|||
{% if request.user_permissions.manage_announcements %}
|
||||
<a href="{% url 'control.announcements' %}">{% trans 'Announcements' %}</a> ·
|
||||
{% endif %}
|
||||
{% if request.user_permissions.manage_map_updates %}
|
||||
<a href="{% url 'control.map_updates' %}">{% trans 'Map Updates' %}</a> ·
|
||||
{% endif %}
|
||||
<a href="{% url 'control.users.detail' user=request.user.pk %}">{{ request.user.username }}</a>
|
||||
</p>
|
||||
</nav>
|
||||
|
|
96
src/c3nav/control/templates/control/map_updates.html
Normal file
96
src/c3nav/control/templates/control/map_updates.html
Normal 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 %}
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
18
src/c3nav/mapdata/migrations/0004_mapupdate_types.py
Normal file
18
src/c3nav/mapdata/migrations/0004_mapupdate_types.py
Normal 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),
|
||||
),
|
||||
]
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue