From 3f432727d9813a3c86cf8e0047769e84aeece613 Mon Sep 17 00:00:00 2001 From: Klaas van Schelven Date: Fri, 29 Nov 2024 15:59:14 +0100 Subject: [PATCH] Warn about lack of EMAIL_BACKEND in the interface --- bugsink/context_processors.py | 34 +++++++++++++++++-- bugsink/urls.py | 4 ++- bugsink/views.py | 15 +++++++- ...stallation_silence_email_system_warning.py | 18 ++++++++++ phonehome/models.py | 5 +++ theme/templates/base.html | 7 ++-- 6 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 phonehome/migrations/0005_installation_silence_email_system_warning.py diff --git a/bugsink/context_processors.py b/bugsink/context_processors.py index 73e19c7..9ea0b23 100644 --- a/bugsink/context_processors.py +++ b/bugsink/context_processors.py @@ -1,7 +1,10 @@ +from collections import namedtuple from datetime import timedelta +from django.conf import settings from django.utils import timezone from django.utils.safestring import mark_safe +from django.urls import reverse from bugsink.app_settings import get_settings, CB_ANYBODY from bugsink.transaction import durable_atomic @@ -11,12 +14,19 @@ from snappea.models import Task from phonehome.models import Installation +SystemWarning = namedtuple('SystemWarning', ['message', 'ignore_url']) + FREE_VERSION_WARNING = mark_safe( """This is the free version of Bugsink; usage is limited to a single user for local development only. Using this software in production requires a paid licence.""") +EMAIL_BACKEND_WARNING = mark_safe( + """Email is not set up, I can't send email. To get the most out of Bugsink, please + set up + an EMAIL_BACKEND.""") + def get_snappea_warnings(): # We warn in either of 2 cases, as documented per-case. @@ -33,8 +43,8 @@ def get_snappea_warnings(): oldest_task_age = (timezone.now() - Task.objects.all().order_by('created_at').first().created_at).seconds - WARNING = (f"Snappea has {task_count} tasks in the queue, the oldest being {oldest_task_age}s old. It may be " - f"either overwhelmed, blocked, not running, or misconfigured.") + WARNING = SystemWarning((f"Snappea has {task_count} tasks in the queue, the oldest being {oldest_task_age}s old. " + f"It may be either overwhelmed, blocked, not running, or misconfigured."), None) # 1. "a lot" of tasks in the backlog. # We have a backlog because spikes are always to be expected, and dealing with them in a backlog is the feature that @@ -67,10 +77,28 @@ def useful_settings_processor(request): nag_7 = installation.created_at < timezone.now() - timedelta(days=7) nag_30 = installation.created_at < timezone.now() - timedelta(days=30) + system_warnings = [] + # (First version of "should I nag" logic): nag only after considerable time to play with the app, and for "some # indication" that you're using this in production (the simplest such indication is that you've configured a # BASE_URL that's not localhost). Subject to change. - system_warnings = [FREE_VERSION_WARNING] if nag_30 and 'localhost' not in get_settings().BASE_URL else [] + if nag_30 and 'localhost' not in get_settings().BASE_URL: + system_warnings.append(SystemWarning(FREE_VERSION_WARNING, None)) + + if settings.EMAIL_BACKEND in [ + 'django.core.mail.backends.console.EmailBackend', + 'bugsink.email_backends.QuietConsoleEmailBackend'] and not installation.silence_email_system_warning: + + if getattr(getattr(request, "user", None), "is_superuser"): + ignore_url = reverse("silence_email_system_warning") + else: + # not a superuser, so can't silence the warning. I'm applying some heuristics here; + # * superusers (and only those) will be able to deal with this (have access to EMAIL_BACKEND) + # * better to still show (though not silencable) the message to non-superusers. + # this will not always be so, but it's a good start. + ignore_url = None + + system_warnings.append(SystemWarning(EMAIL_BACKEND_WARNING, ignore_url)) return { # Note: no way to actually set the license key yet, so nagging always happens for now. diff --git a/bugsink/urls.py b/bugsink/urls.py index ba4a32a..1dcea6e 100644 --- a/bugsink/urls.py +++ b/bugsink/urls.py @@ -11,7 +11,7 @@ from bugsink.app_settings import get_settings from users.views import signup, confirm_email, resend_confirmation, request_reset_password, reset_password, preferences from ingest.views import download_envelope -from .views import home, trigger_error, favicon, settings_view +from .views import home, trigger_error, favicon, settings_view, silence_email_system_warning from .debug_views import csrf_debug @@ -49,6 +49,8 @@ urlpatterns = [ path('issues/', include('issues.urls')), path('admin/', admin.site.urls), + + path('silence-email-system-warning/', silence_email_system_warning, name='silence_email_system_warning'), path('settings/', settings_view, name='settings'), path('debug/csrf/', csrf_debug, name='csrf_debug'), diff --git a/bugsink/views.py b/bugsink/views.py index 4699ca6..e53155d 100644 --- a/bugsink/views.py +++ b/bugsink/views.py @@ -1,6 +1,6 @@ import sys -from django.http import HttpResponseServerError, HttpResponseBadRequest +from django.http import HttpResponseServerError, HttpResponseBadRequest, HttpResponseRedirect from django.template import TemplateDoesNotExist, loader from django.views.decorators.csrf import requires_csrf_token from django.views.defaults import ERROR_500_TEMPLATE_NAME, ERROR_PAGE_TEMPLATE, ERROR_400_TEMPLATE_NAME @@ -19,8 +19,10 @@ from snappea.settings import get_settings as get_snappea_settings from bugsink.version import __version__ from bugsink.decorators import login_exempt from bugsink.app_settings import get_settings as get_bugsink_settings +from bugsink.decorators import atomic_for_request_method from phonehome.tasks import send_if_due +from phonehome.models import Installation def _phone_home(): @@ -116,6 +118,17 @@ def settings_view(request): }) +@user_passes_test(lambda u: u.is_superuser) +@atomic_for_request_method +def silence_email_system_warning(request): + installation = Installation.objects.get() + installation.silence_email_system_warning = True + installation.save() + + next = request.POST.get("next", "/") + return HttpResponseRedirect(next) + + @requires_csrf_token def bad_request(request, exception, template_name=ERROR_400_TEMPLATE_NAME): # verbatim copy of Django's default bad_request view, but with "exception" in the context diff --git a/phonehome/migrations/0005_installation_silence_email_system_warning.py b/phonehome/migrations/0005_installation_silence_email_system_warning.py new file mode 100644 index 0000000..8715a78 --- /dev/null +++ b/phonehome/migrations/0005_installation_silence_email_system_warning.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-11-29 13:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("phonehome", "0004_installation_created_at"), + ] + + operations = [ + migrations.AddField( + model_name="installation", + name="silence_email_system_warning", + field=models.BooleanField(default=False), + ), + ] diff --git a/phonehome/models.py b/phonehome/models.py index 15a3610..d51a57d 100644 --- a/phonehome/models.py +++ b/phonehome/models.py @@ -4,9 +4,14 @@ from django.db import models class Installation(models.Model): + # "Installation" would probably be better at home in some different app, especially now that we're adding more and + # more stuff to it. But cross-app migrations are annoying enough and the upside is _very_ limited so it stays here. + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) created_at = models.DateTimeField(auto_now_add=True) + silence_email_system_warning = models.BooleanField(default=False) + class OutboundMessage(models.Model): attempted_at = models.DateTimeField(auto_now_add=True) diff --git a/theme/templates/base.html b/theme/templates/base.html index fd4738c..2938bd9 100644 --- a/theme/templates/base.html +++ b/theme/templates/base.html @@ -47,8 +47,11 @@ {% for system_warning in system_warnings %}
- {{ system_warning }} + {{ system_warning.message }}
+ {% if system_warning.ignore_url %} +
{% csrf_token %}
{# the button adds some undesired padding; leaving it for now #} + {% endif %}
{% endfor %} @@ -58,7 +61,7 @@ Foo bar baz -
+
{# heroicon's x-circle #}