implement SiteUpdates: make the user reload on critical code changes

This commit is contained in:
Laura Klünder 2017-12-24 01:55:30 +01:00
parent 8503b2554c
commit cdfef25034
7 changed files with 84 additions and 11 deletions

View file

@ -418,7 +418,9 @@ class UpdatesViewSet(GenericViewSet):
except ValueError: except ValueError:
cache.set('api_updates_fetch_requests', 0, None) cache.set('api_updates_fetch_requests', 0, None)
from c3nav.site.models import SiteUpdate
response = Response({ response = Response({
'last_site_update': SiteUpdate.last_update(),
'last_map_update': MapUpdate.current_processed_cache_key(), 'last_map_update': MapUpdate.current_processed_cache_key(),
'user': get_user_data(request), 'user': get_user_data(request),
}) })

View file

@ -163,7 +163,7 @@ class MapUpdate(models.Model):
new_update.save() new_update.save()
transaction.on_commit( transaction.on_commit(
lambda: cache.set('mapdata:last_processed_update', new_updates[-1].to_tuple, 300) lambda: cache.set('mapdata:last_processed_update', new_updates[-1].to_tuple, None)
) )
return new_updates return new_updates
@ -188,7 +188,7 @@ class MapUpdate(models.Model):
if new: if new:
transaction.on_commit( transaction.on_commit(
lambda: cache.set('mapdata:last_update', self.to_tuple, 300) lambda: cache.set('mapdata:last_update', self.to_tuple, None)
) )
if settings.HAS_CELERY: if settings.HAS_CELERY:
transaction.on_commit( transaction.on_commit(

View file

@ -8,7 +8,7 @@ class Command(BaseCommand):
result = input('Type YES to create a new site update: ') result = input('Type YES to create a new site update: ')
if result == 'YES': if result == 'YES':
from c3nav.mapdata.models import SiteUpdate from c3nav.site.models import SiteUpdate
SiteUpdate.objects.create() SiteUpdate.objects.create()
print('New site update created.') print('New site update created.')
else: else:

View file

@ -1,6 +1,8 @@
from contextlib import contextmanager
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.db import models from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -50,3 +52,42 @@ class SiteUpdate(models.Model):
A site update that asks the user to reload the page. A site update that asks the user to reload the page.
""" """
created = models.DateTimeField(auto_now_add=True, verbose_name=_('create')) created = models.DateTimeField(auto_now_add=True, verbose_name=_('create'))
class Meta:
verbose_name = _('Site update')
verbose_name_plural = _('Site updates')
default_related_name = 'siteupdates'
get_latest_by = 'created'
@classmethod
@contextmanager
def lock(cls):
with transaction.atomic():
try:
yield cls.objects.select_for_update().get(pk=cls.objects.earliest().pk)
except cls.DoesNotExist:
yield
@classmethod
def last_update(cls):
last_update = cache.get('site:last_site_update', None)
if last_update is not None:
return last_update
with cls.lock():
try:
last_update = cls.objects.latest()
except cls.DoesNotExist:
last_update = None
else:
last_update = last_update.pk
cache.set('site:last_site_update', last_update, None)
return last_update
def save(self, **kwargs):
new = self.pk is None
with transaction.atomic():
super().save(**kwargs)
if new:
transaction.on_commit(
lambda: cache.set('site:last_site_update', self.pk, None)
)

View file

@ -68,6 +68,9 @@ c3nav = {
state = JSON.parse($main.attr('data-state')); state = JSON.parse($main.attr('data-state'));
c3nav.embed = $main.is('[data-embed]'); c3nav.embed = $main.is('[data-embed]');
c3nav.last_site_update = JSON.parse($main.attr('data-last-site-update'));
c3nav.new_site_update = false;
history.replaceState(state, window.location.path); history.replaceState(state, window.location.path);
c3nav.load_state(state, true); c3nav.load_state(state, true);
c3nav.update_map_locations(); c3nav.update_map_locations();
@ -549,10 +552,13 @@ c3nav = {
// console.log('state pushed'); // console.log('state pushed');
history.pushState(state, '', url); history.pushState(state, '', url);
} }
c3nav._maybe_load_site_update(state);
}, },
_onpopstate: function (e) { _onpopstate: function (e) {
// console.log('state popped'); // console.log('state popped');
c3nav.load_state(e.state); c3nav.load_state(e.state);
c3nav._maybe_load_site_update(e.state);
}, },
load_state: function (state, nofly) { load_state: function (state, nofly) {
if (state.modal) { if (state.modal) {
@ -886,22 +892,24 @@ c3nav = {
} }
}, },
open_modal: function (content) { modal_noclose: false,
open_modal: function (content, no_close) {
c3nav.modal_noclose = no_close;
var $modal = $('#modal'); var $modal = $('#modal');
c3nav._set_modal_content(content); c3nav._set_modal_content(content, no_close);
if (!$modal.is('.show')) { if (!$modal.is('.show')) {
c3nav._push_state({modal: true, sidebar: true}); c3nav._push_state({modal: true, sidebar: true});
$modal.addClass('show'); $modal.addClass('show');
} }
}, },
_set_modal_content: function(content) { _set_modal_content: function(content, no_close) {
$('#modal').toggleClass('loading', !content) $('#modal').toggleClass('loading', !content)
.find('#modal-content') .find('#modal-content')
.html('<button class="button-clear material-icons" id="close-modal">clear</button>') .html((!no_close) ? '<button class="button-clear material-icons" id="close-modal">clear</button>' :'')
.append(content || ''); .append(content || '');
}, },
_modal_click: function(e) { _modal_click: function(e) {
if (e.target.id === 'modal' || e.target.id === 'close-modal') { if (!c3nav.modal_noclose && (e.target.id === 'modal' || e.target.id === 'close-modal')) {
history.back(); history.back();
} }
}, },
@ -1168,8 +1176,26 @@ c3nav = {
}, },
_fetch_updates_callback: function (data) { _fetch_updates_callback: function (data) {
c3nav.schedule_fetch_updates(); c3nav.schedule_fetch_updates();
if (c3nav.last_site_update !== data.last_site_update) {
c3nav.new_site_update = true;
c3nav.last_site_update = data.last_site_update;
c3nav._maybe_load_site_update(c3nav.state);
}
c3nav._set_user_data(data.user); c3nav._set_user_data(data.user);
}, },
_maybe_load_site_update: function(state) {
if (c3nav.new_site_update && !state.modal && (!state.routing || !state.origin || !state.destination)) {
c3nav._load_site_update();
}
},
_load_site_update: function() {
$('#modal-content').css({
width: 'auto',
minHeight: 0
});
c3nav.open_modal($('.reload-msg').html(), true);
window.location.reload();
},
_set_user_data: function (data) { _set_user_data: function (data) {
var $user = $('header #user'); var $user = $('header #user');
$user.find('span').text(data.title); $user.find('span').text(data.title);

View file

@ -4,7 +4,7 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<main class="map" data-state="{{ state }}"{% if embed %} data-embed{% endif %}> <main class="map" data-state="{{ state }}"{% if embed %} data-embed{% endif %} data-last-site-update="{{ last_site_update }}">
<section id="attributions"> <section id="attributions">
{% if not embed %} {% if not embed %}
{% get_current_language as CURRENT_LANGUAGE %} {% get_current_language as CURRENT_LANGUAGE %}
@ -37,6 +37,9 @@
<button class="mobileclient-shortcut">{% trans 'create shortcut' %}</button> <button class="mobileclient-shortcut">{% trans 'create shortcut' %}</button>
</p> </p>
</section> </section>
<section class="reload-msg">
<img src="{% static 'img/loader.gif' %}">
</section>
<section id="sidebar"> <section id="sidebar">
<section id="search" class="loading"> <section id="search" class="loading">
<div class="location locationinput empty" id="origin-input"> <div class="location locationinput empty" id="origin-input">

View file

@ -28,7 +28,7 @@ from c3nav.mapdata.models.locations import LocationRedirect, SpecificLocation
from c3nav.mapdata.utils.locations import get_location_by_slug_for_request, levels_by_short_label_for_request from c3nav.mapdata.utils.locations import get_location_by_slug_for_request, levels_by_short_label_for_request
from c3nav.mapdata.utils.user import get_user_data from c3nav.mapdata.utils.user import get_user_data
from c3nav.mapdata.views import set_tile_access_cookie from c3nav.mapdata.views import set_tile_access_cookie
from c3nav.site.models import Announcement from c3nav.site.models import Announcement, SiteUpdate
def check_location(location: Optional[str], request) -> Optional[SpecificLocation]: def check_location(location: Optional[str], request) -> Optional[SpecificLocation]:
@ -120,6 +120,7 @@ def map_index(request, mode=None, slug=None, slug2=None, details=None, options=N
'tile_cache_server': settings.TILE_CACHE_SERVER, 'tile_cache_server': settings.TILE_CACHE_SERVER,
'initial_level': settings.INITIAL_LEVEL, 'initial_level': settings.INITIAL_LEVEL,
'initial_bounds': json.dumps(settings.INITIAL_BOUNDS, separators=(',', ':')), 'initial_bounds': json.dumps(settings.INITIAL_BOUNDS, separators=(',', ':')),
'last_site_update': json.dumps(SiteUpdate.last_update()),
'embed': bool(embed), 'embed': bool(embed),
} }