Sunil Mohan Adapa f240c4203c
upgrades: Show better error messages
- Reduce the number of specialized messages to ease localization and clear way
for generalized configuration change handler.

Tests:

- Update the one or two configuration setting at the same time and notice that a
single message is shown.

- When no setting is changed and form is submitted, 'settings unchanged' message
is shown.

- Raise error when enabling/disable auto updates and notice a proper HTML error
shown. When other setting is also updated, then one error and one success
message is shown.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2024-03-19 11:46:41 -04:00

221 lines
7.6 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""FreedomBox app for upgrades."""
import subprocess
from apt.cache import Cache
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from plinth import __version__
from plinth.modules import first_boot, upgrades
from plinth.privileged import packages as packages_privileged
from plinth.views import AppView, messages_error
from . import privileged
from .forms import BackportsFirstbootForm, ConfigureForm, UpdateFirstbootForm
class UpgradesConfigurationView(AppView):
"""Serve configuration page."""
form_class = ConfigureForm
success_url = reverse_lazy('upgrades:index')
template_name = "upgrades_configure.html"
app_id = 'upgrades'
def get_initial(self):
"""Return the initial values for the form."""
return {
'auto_upgrades_enabled': privileged.check_auto(),
'dist_upgrade_enabled': upgrades.is_dist_upgrade_enabled()
}
def get_context_data(self, *args, **kwargs):
"""Add additional context data for template."""
context = super().get_context_data(*args, **kwargs)
context['can_activate_backports'] = upgrades.can_activate_backports()
context['is_backports_requested'] = upgrades.is_backports_requested()
context['is_busy'] = (_is_updating()
or packages_privileged.is_package_manager_busy())
context['log'] = privileged.get_log()
context['refresh_page_sec'] = 3 if context['is_busy'] else None
context['version'] = __version__
context['new_version'] = is_newer_version_available()
context['os_release'] = get_os_release()
context['can_test_dist_upgrade'] = upgrades.can_test_dist_upgrade()
return context
def form_valid(self, form):
"""Apply the form changes."""
old_status = form.initial
new_status = form.cleaned_data
is_changed = False
if old_status['auto_upgrades_enabled'] \
!= new_status['auto_upgrades_enabled']:
try:
if new_status['auto_upgrades_enabled']:
privileged.enable_auto()
else:
privileged.disable_auto()
is_changed = True
except Exception as exception:
messages_error(self.request,
_('Error when configuring unattended-upgrades'),
exception)
if old_status['dist_upgrade_enabled'] \
!= new_status['dist_upgrade_enabled']:
upgrades.set_dist_upgrade_enabled(
new_status['dist_upgrade_enabled'])
is_changed = True
if is_changed:
messages.success(self.request, _('Configuration updated.'))
return super().form_valid(form)
def is_newer_version_available():
"""Return whether a newer Freedombox version is available."""
cache = Cache()
freedombox = cache['freedombox']
return not freedombox.candidate.is_installed
def get_os_release():
"""Return the Debian release number and name."""
output = 'Error: Cannot read PRETTY_NAME in /etc/os-release.'
with open('/etc/os-release', 'r', encoding='utf-8') as release_file:
for line in release_file:
if 'PRETTY_NAME=' in line:
line = line.replace('"', '').strip()
line = line.split('=')
output = line[1]
return output
def _is_updating():
"""Check if manually triggered update is running."""
command = ['systemctl', 'is-active', 'freedombox-manual-upgrade']
result = subprocess.run(command, capture_output=True, text=True,
check=False)
return str(result.stdout).startswith('activ') # 'active' or 'activating'
def upgrade(request):
"""Serve the upgrade page."""
if request.method == 'POST':
try:
privileged.run()
messages.success(request, _('Upgrade process started.'))
except Exception:
messages.error(request, _('Starting upgrade failed.'))
return redirect(reverse_lazy('upgrades:index'))
def activate_backports(request):
"""Activate backports."""
if request.method == 'POST':
upgrades.set_backports_requested(True)
upgrades.setup_repositories(None)
messages.success(request, _('Frequent feature updates activated.'))
return redirect(reverse_lazy('upgrades:index'))
class BackportsFirstbootView(FormView):
"""View to configure backports during first boot wizard."""
template_name = 'backports-firstboot.html'
form_class = BackportsFirstbootForm
def dispatch(self, request, *args, **kwargs):
"""Show backports configuration form only if it can be activated."""
if upgrades.is_backports_enabled():
# Backports is already enabled. Record this preference and
# skip first boot step.
upgrades.set_backports_requested(True)
first_boot.mark_step_done('backports_wizard')
return HttpResponseRedirect(reverse_lazy(first_boot.next_step()))
if not upgrades.can_activate_backports():
# Skip first boot step.
upgrades.set_backports_requested(False)
first_boot.mark_step_done('backports_wizard')
return HttpResponseRedirect(reverse_lazy(first_boot.next_step()))
return super().dispatch(request, *args, *kwargs)
def get_success_url(self):
"""Return next firstboot step."""
return reverse_lazy(first_boot.next_step())
def form_valid(self, form):
"""Mark the first wizard step as done, save value and redirect."""
enabled = form.cleaned_data['backports_enabled']
upgrades.set_backports_requested(enabled)
upgrades.setup_repositories(None)
first_boot.mark_step_done('backports_wizard')
return super().form_valid(form)
class UpdateFirstbootView(FormView):
"""View to run initial update during first boot wizard."""
template_name = 'update-firstboot.html'
form_class = UpdateFirstbootForm
def __init__(self):
"""Define instance attribute."""
self.update = True
def get_success_url(self):
"""Return next firstboot step."""
if self.update:
return reverse_lazy('upgrades:update-firstboot-progress')
return reverse_lazy(first_boot.next_step())
def form_valid(self, form):
"""Run update if selected, and mark step as done."""
self.update = form.cleaned_data['update_now']
if self.update:
privileged.run()
first_boot.mark_step_done('initial_update')
return super().form_valid(form)
class UpdateFirstbootProgressView(TemplateView):
"""View to show initial update progress."""
template_name = 'update-firstboot-progress.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['is_busy'] = (_is_updating()
or packages_privileged.is_package_manager_busy())
context['next_step'] = first_boot.next_step()
context['refresh_page_sec'] = 3 if context['is_busy'] else None
return context
def test_dist_upgrade(request):
"""Test dist-upgrade from stable to testing."""
if request.method == 'POST':
upgrades.test_dist_upgrade()
messages.success(request, _('Starting distribution upgrade test.'))
return redirect(reverse_lazy('upgrades:index'))