Automatic update checks (#434)

* Basic update checks

* Fix issues pointed out by @rixx

* First test

* Add tests

* Even more tests
This commit is contained in:
Raphael Michel
2017-04-01 15:34:34 +02:00
committed by GitHub
parent 867a8132aa
commit 4919f8991c
17 changed files with 625 additions and 12 deletions

View File

@@ -3,6 +3,8 @@ import sys
from django.conf import settings
from django.core.urlresolvers import Resolver404, get_script_prefix, resolve
from pretix.base.settings import GlobalSettingsObject
from .signals import html_head, nav_event, nav_topbar
from .utils.i18n import get_javascript_format, get_moment_locale
@@ -53,4 +55,13 @@ def contextprocessor(request):
elif 'runserver' in sys.argv:
ctx['development_warning'] = True
ctx['warning_update_available'] = False
ctx['warning_update_check_active'] = False
if request.user.is_superuser:
gs = GlobalSettingsObject()
if gs.settings.update_check_result_warning:
ctx['warning_update_available'] = True
if not gs.settings.update_check_ack and 'runserver' not in sys.argv:
ctx['warning_update_check_active'] = True
return ctx

View File

@@ -1,5 +1,6 @@
from collections import OrderedDict
from django import forms
from django.utils.translation import ugettext_lazy as _
from i18nfield.forms import I18nFormField, I18nTextInput
@@ -32,3 +33,26 @@ class GlobalSettingsForm(SettingsForm):
for key, value in response.items():
# We need to be this explicit, since OrderedDict.update does not retain ordering
self.fields[key] = value
class UpdateSettingsForm(SettingsForm):
update_check_perform = forms.BooleanField(
required=False,
label=_("Perform update checks"),
help_text=_("During the update check, pretix will report an anonymous, unique installation ID, "
"the current version of pretix and your installed plugins and the number of active and "
"inactive events in your installation to servers operated by the pretix developers. We "
"will only store anonymous data, never any IP adresses and we will not know who you are "
"or where to find your instance. You can disable this behaviour here at any time.")
)
update_check_email = forms.EmailField(
required=False,
label=_("E-mail notifications"),
help_text=_("We will notify you at this address if we detect that a new update is available. This "
"address will not be transmitted to pretix.eu, the emails will be sent by this server "
"locally.")
)
def __init__(self, *args, **kwargs):
self.obj = GlobalSettingsObject()
super().__init__(*args, obj=self.obj, **kwargs)

View File

@@ -105,6 +105,13 @@
</li>
{% endfor %}
{% if warning_update_available %}
<li>
<a href="{% url 'control:global.update' %}" class="danger">
<i class="fa fa-bell"></i>
</a>
</li>
{% endif %}
<li>
<a href="{% url 'control:user.settings' %}">
<i class="fa fa-user"></i> {{ request.user.get_full_name }}
@@ -141,7 +148,8 @@
</li>
{% if request.user.is_superuser %}
<li>
<a href="{% url 'control:global-settings' %}" {% if "global-settings" in url_name %}class="active"{% endif %}>
<a href="{% url 'control:global.settings' %}"
{% if "global.settings" in url_name %}class="active"{% endif %}>
<i class="fa fa-wrench fa-fw"></i>
{% trans "Global settings" %}
</a>
@@ -173,6 +181,19 @@
</div>
{% endfor %}
{% endif %}
{% if warning_update_check_active %}
<div class="alert alert-info">
<a href="{% url "control:global.update" %}">
{% blocktrans trimmed %}
Starting with version 1.2.0, pretix automatically checks for updates in the background.
During this check, anonymous data is transmitted to servers operated by pretix'
developers. Click on this message to find out more, disable this feature or enter your
email address to get notified via email if a new update arrives. This message will
disappear once you clicked it.
{% endblocktrans %}
</a>
</div>
{% endif %}
{% if debug_warning %}
<div class="alert alert-danger">

View File

@@ -1,10 +1,8 @@
{% extends "pretixcontrol/base.html" %}
{% extends "pretixcontrol/global_settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Global settings" %}{% endblock %}
{% block content %}
<h1>{% trans "Global settings" %}</h1>
{% block inner %}
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form_errors form %}

View File

@@ -0,0 +1,22 @@
{% extends "pretixcontrol/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Global settings" %}{% endblock %}
{% block content %}
<h1>{% trans "Global settings" %}</h1>
<ul class="nav nav-pills">
<li {% if "global.settings" == url_name %}class="active"{% endif %}>
<a href="{% url 'control:global.settings' %}">
{% trans "General" %}
</a>
</li>
<li {% if "global.update" == url_name %}class="active"{% endif %}>
<a href="{% url 'control:global.update' %}">
{% trans "Update check" %}
</a>
</li>
</ul>
{% block inner %}
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,90 @@
{% extends "pretixcontrol/global_settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<fieldset>
<legend>{% trans "Update check results" %}</legend>
{% if not gs.settings.update_check_perform %}
<div class="alert alert-warning">
{% trans "Update checks are disabled." %}
</div>
{% elif not gs.settings.update_check_last %}
<div class="alert alert-info">
{% trans "No update check has been performed yet since the last update of this installation. Update checks are performed on a daily basis if your cronjob is set up properly." %}
</div>
<form action="" method="post">
{% csrf_token %}
<p>
<button type="submit" name="trigger" value="1" class="btn btn-default">
{% trans "Check for updates now" %}
</button>
</p>
</form>
{% elif "error" in gs.settings.update_check_result %}
<div class="alert alert-danger">
{% trans "The last update check was not successful." %}
{% if gs.settings.update_check_result.error == "http_error" %}
{% trans "The pretix.eu server returned an error code." %}
{% elif gs.settings.update_check_result.error == "unavailable" %}
{% trans "The pretix.eu server could not be reached." %}
{% elif gs.settings.update_check_result.error == "development" %}
{% trans "This installation appears to be a development installation." %}
{% endif %}
</div>
<form action="" method="post">
{% csrf_token %}
<p>
<button type="submit" name="trigger" value="1" class="btn btn-default">
{% trans "Check for updates now" %}
</button>
</p>
</form>
{% else %}
<form action="" method="post">
{% csrf_token %}
<p>
{% blocktrans trimmed with date=gs.settings.update_check_last|date:"SHORT_DATETIME_FORMAT" %}
Last updated: {{ date }}
{% endblocktrans %}
<button type="submit" name="trigger" value="1" class="btn btn-default btn-xs">
{% trans "Check for updates now" %}
</button>
</p>
</form>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<th>{% trans "Component" %}</th>
<th>{% trans "Installed version" %}</th>
<th>{% trans "Latest version" %}</th>
</tr>
</thead>
<tbody>
{% for row in tbl %}
<tr class="{% if row.3 %}danger{% elif row.2 == "?" %}warning{% else %}success{% endif %}">
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
<td>{{ row.2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</fieldset>
<form action="" method="post" class="form-horizontal" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<legend>{% trans "Update check settings" %}</legend>
{% bootstrap_form_errors form %}
{% bootstrap_form form layout='horizontal' %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -14,9 +14,10 @@ urlpatterns = [
url(r'^forgot$', auth.Forgot.as_view(), name='auth.forgot'),
url(r'^forgot/recover$', auth.Recover.as_view(), name='auth.forgot.recover'),
url(r'^$', dashboards.user_index, name='index'),
url(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global-settings'),
url(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'),
url(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'),
url(r'^settings$', user.UserSettings.as_view(), name='user.settings'),
url(r'^settings/?$', user.UserSettings.as_view(), name='user.settings'),
url(r'^settings/2fa/$', user.User2FAMainView.as_view(), name='user.settings.2fa'),
url(r'^settings/history/$', user.UserHistoryView.as_view(), name='user.settings.history'),
url(r'^settings/2fa/add$', user.User2FADeviceAddView.as_view(), name='user.settings.2fa.add'),

View File

@@ -1,9 +1,13 @@
from django.contrib import messages
from django.shortcuts import reverse
from django.shortcuts import redirect, reverse
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView
from pretix.control.forms.global_settings import GlobalSettingsForm
from pretix.base.services.update_check import check_result_table, update_check
from pretix.base.settings import GlobalSettingsObject
from pretix.control.forms.global_settings import (
GlobalSettingsForm, UpdateSettingsForm,
)
from pretix.control.permissions import AdministratorPermissionRequiredMixin
@@ -21,4 +25,34 @@ class GlobalSettingsView(AdministratorPermissionRequiredMixin, FormView):
return super().form_invalid(form)
def get_success_url(self):
return reverse('control:global-settings')
return reverse('control:global.settings')
class UpdateCheckView(AdministratorPermissionRequiredMixin, FormView):
template_name = 'pretixcontrol/global_update.html'
form_class = UpdateSettingsForm
def post(self, request, *args, **kwargs):
if 'trigger' in request.POST:
update_check.apply()
return redirect(self.get_success_url())
return super().post(request, *args, **kwargs)
def form_valid(self, form):
form.save()
messages.success(self.request, _('Your changes have been saved.'))
return super().form_valid(form)
def form_invalid(self, form):
messages.error(self.request, _('Your changes have not been saved, see below for errors.'))
return super().form_invalid(form)
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['gs'] = GlobalSettingsObject()
ctx['gs'].settings.set('update_check_ack', True)
ctx['tbl'] = check_result_table()
return ctx
def get_success_url(self):
return reverse('control:global.update')