Give admins option to test if a theme loads correctly

If a theme is uploaded incorrectly or has errors in it, users can still
select the theme but it will cause a 500 error on every page, making the
app unusable and also making it impossible for them to switch to a
functional theme.

A better fix would be to fail gracefully, but in lieu of that, this will
at least let admins confirm if a theme is broken safely.
This commit is contained in:
Mouse Reeve 2023-11-20 09:56:51 -08:00
parent 4da96d937e
commit d828ba0bc6
6 changed files with 88 additions and 1 deletions

View file

@ -0,0 +1,18 @@
# Generated by Django 3.2.23 on 2023-11-20 17:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0186_invite_request_notification"),
]
operations = [
migrations.AddField(
model_name="theme",
name="loads",
field=models.BooleanField(blank=True, null=True),
),
]

View file

@ -149,6 +149,7 @@ class Theme(SiteModel):
created_date = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50, unique=True)
path = models.CharField(max_length=50, unique=True)
loads = models.BooleanField(null=True, blank=True)
def __str__(self):
# pylint: disable=invalid-str-returned

View file

@ -12,6 +12,15 @@
{% endblock %}
{% block panel %}
{% if broken_theme %}
<div class="notification is-danger">
<span class="icon icon-warning" aria-hidden="true"></span>
<span>
{% trans "One of your themes appears to be broken. Selecting this theme will make the application unusable." %}
</span>
</div>
{% endif %}
{% if success %}
<div class="notification is-success is-light">
<span class="icon icon-check" aria-hidden="true"></span>
@ -98,6 +107,9 @@
<th>
{% trans "Actions" %}
</th>
<th>
{% trans "Status" %}
</th>
</tr>
{% for theme in themes %}
<tr>
@ -112,6 +124,37 @@
</button>
</form>
</td>
<td>
{% if theme.loads is None %}
<form method="POST" action="{% url 'settings-themes-test' theme.id %}">
{% csrf_token %}
<button type="submit" class="button is-small">
<span class="icon icon-question-circle" aria-hidden="true"></span>
<span>{% trans "Test theme" %}</span>
</button>
</form>
{% elif not theme.loads %}
<span class="tag is-danger">
<span class="icon icon-warning" aria-hidden="true"></span>
<span>
{% trans "Broken theme" %}
</span>
</span>
{% else %}
<span class="tag is-success">
<span class="icon icon-check" aria-hidden="true"></span>
<span>
{% trans "Loaded successfully" %}
</span>
</span>
{% endif %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -109,6 +109,11 @@ urlpatterns = [
views.delete_theme,
name="settings-themes-delete",
),
re_path(
r"^settings/themes/(?P<theme_id>\d+)/test/?$",
views.test_theme,
name="settings-themes-test",
),
re_path(
r"^settings/announcements/?$",
views.Announcements.as_view(),

View file

@ -30,7 +30,7 @@ from .admin.reports import (
moderator_delete_user,
)
from .admin.site import Site, Registration, RegistrationLimited
from .admin.themes import Themes, delete_theme
from .admin.themes import Themes, delete_theme, test_theme
from .admin.user_admin import UserAdmin, UserAdminList, ActivateUserAdmin
# user preferences

View file

@ -6,6 +6,8 @@ from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.http import require_POST
from sass_processor.processor import sass_processor
from bookwyrm import forms, models
@ -40,6 +42,7 @@ class Themes(View):
def get_view_data():
"""data for view"""
return {
"broken_theme": models.Theme.objects.filter(loads=False).exists(),
"themes": models.Theme.objects.all(),
"theme_form": forms.ThemeForm(),
}
@ -52,3 +55,20 @@ def delete_theme(request, theme_id):
"""Remove a theme"""
get_object_or_404(models.Theme, id=theme_id).delete()
return redirect("settings-themes")
@require_POST
@permission_required("bookwyrm.system_administration", raise_exception=True)
# pylint: disable=unused-argument
def test_theme(request, theme_id):
"""Remove a theme"""
theme = get_object_or_404(models.Theme, id=theme_id)
try:
sass_processor(theme.path)
theme.loads = True
except Exception: # pylint: disable=broad-except
theme.loads = False
theme.save()
return redirect("settings-themes")