mirror of
https://github.com/simonw/datasette.git
synced 2026-09-30 13:54:07 +02:00
Flash messages mechanism, closes #790
This commit is contained in:
parent
1d0bea157a
commit
4fa7cf6853
14 changed files with 217 additions and 7 deletions
|
|
@ -2,6 +2,7 @@ import asyncio
|
|||
import collections
|
||||
import datetime
|
||||
import hashlib
|
||||
from http.cookies import SimpleCookie
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
|
|
@ -30,6 +31,7 @@ from .views.special import (
|
|||
PatternPortfolioView,
|
||||
AuthTokenView,
|
||||
PermissionsDebugView,
|
||||
MessagesDebugView,
|
||||
)
|
||||
from .views.table import RowView, TableView
|
||||
from .renderer import json_renderer
|
||||
|
|
@ -156,6 +158,11 @@ async def favicon(scope, receive, send):
|
|||
|
||||
|
||||
class Datasette:
|
||||
# Message constants:
|
||||
INFO = 1
|
||||
WARNING = 2
|
||||
ERROR = 3
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
files,
|
||||
|
|
@ -423,6 +430,38 @@ class Datasette:
|
|||
# pylint: disable=no-member
|
||||
pm.hook.prepare_connection(conn=conn, database=database, datasette=self)
|
||||
|
||||
def add_message(self, request, message, type=INFO):
|
||||
if not hasattr(request, "_messages"):
|
||||
request._messages = []
|
||||
request._messages_should_clear = False
|
||||
request._messages.append((message, type))
|
||||
|
||||
def _write_messages_to_response(self, request, response):
|
||||
if getattr(request, "_messages", None):
|
||||
# Set those messages
|
||||
cookie = SimpleCookie()
|
||||
cookie["ds_messages"] = self.sign(request._messages, "messages")
|
||||
cookie["ds_messages"]["path"] = "/"
|
||||
# TODO: Co-exist with existing set-cookie headers
|
||||
assert "set-cookie" not in response.headers
|
||||
response.headers["set-cookie"] = cookie.output(header="").lstrip()
|
||||
elif getattr(request, "_messages_should_clear", False):
|
||||
cookie = SimpleCookie()
|
||||
cookie["ds_messages"] = ""
|
||||
cookie["ds_messages"]["path"] = "/"
|
||||
# TODO: Co-exist with existing set-cookie headers
|
||||
assert "set-cookie" not in response.headers
|
||||
response.headers["set-cookie"] = cookie.output(header="").lstrip()
|
||||
|
||||
def _show_messages(self, request):
|
||||
if getattr(request, "_messages", None):
|
||||
request._messages_should_clear = True
|
||||
messages = request._messages
|
||||
request._messages = []
|
||||
return messages
|
||||
else:
|
||||
return []
|
||||
|
||||
async def permission_allowed(
|
||||
self, actor, action, resource_type=None, resource_identifier=None, default=False
|
||||
):
|
||||
|
|
@ -808,6 +847,9 @@ class Datasette:
|
|||
add_route(
|
||||
PermissionsDebugView.as_asgi(self), r"/-/permissions$",
|
||||
)
|
||||
add_route(
|
||||
MessagesDebugView.as_asgi(self), r"/-/messages$",
|
||||
)
|
||||
add_route(
|
||||
PatternPortfolioView.as_asgi(self), r"/-/patterns$",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -351,3 +351,19 @@ p.zero-results {
|
|||
.type-float, .type-int {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.message-info {
|
||||
padding: 1em;
|
||||
border: 1px solid green;
|
||||
background-color: #c7fbc7;
|
||||
}
|
||||
.message-warning {
|
||||
padding: 1em;
|
||||
border: 1px solid #ae7100;
|
||||
background-color: #fbdda5;
|
||||
}
|
||||
.message-error {
|
||||
padding: 1em;
|
||||
border: 1px solid red;
|
||||
background-color: pink;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@
|
|||
<nav class="hd">{% block nav %}{% endblock %}</nav>
|
||||
|
||||
<div class="bd">
|
||||
{% block messages %}
|
||||
{% if show_messages %}
|
||||
{% for message, message_type in show_messages() %}
|
||||
<p class="message-{% if message_type == 1 %}info{% elif message_type == 2 %}warning{% elif message_type == 3 %}error{% endif %}">{{ message }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
|
|
|||
26
datasette/templates/messages_debug.html
Normal file
26
datasette/templates/messages_debug.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Debug messages{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Debug messages</h1>
|
||||
|
||||
<p>Set a message:</p>
|
||||
|
||||
<form action="/-/messages" method="POST">
|
||||
<div>
|
||||
<input type="text" name="message" style="width: 40%">
|
||||
<div class="select-wrapper">
|
||||
<select name="message_type">
|
||||
<option>INFO</option>
|
||||
<option>WARNING</option>
|
||||
<option>ERROR</option>
|
||||
<option>all</option>
|
||||
</select>
|
||||
</div>
|
||||
<input type="submit" value="Add message">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -180,9 +180,9 @@ class AsgiLifespan:
|
|||
|
||||
|
||||
class AsgiView:
|
||||
def dispatch_request(self, request, *args, **kwargs):
|
||||
async def dispatch_request(self, request, *args, **kwargs):
|
||||
handler = getattr(self, request.method.lower(), None)
|
||||
return handler(request, *args, **kwargs)
|
||||
return await handler(request, *args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def as_asgi(cls, *class_args, **class_kwargs):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import asyncio
|
||||
import csv
|
||||
import itertools
|
||||
from itsdangerous import BadSignature
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
|
@ -73,6 +74,20 @@ class BaseView(AsgiView):
|
|||
def database_color(self, database):
|
||||
return "ff0000"
|
||||
|
||||
async def dispatch_request(self, request, *args, **kwargs):
|
||||
# Populate request_messages if ds_messages cookie is present
|
||||
if self.ds:
|
||||
try:
|
||||
request._messages = self.ds.unsign(
|
||||
request.cookies.get("ds_messages", ""), "messages"
|
||||
)
|
||||
except BadSignature:
|
||||
pass
|
||||
response = await super().dispatch_request(request, *args, **kwargs)
|
||||
if self.ds:
|
||||
self.ds._write_messages_to_response(request, response)
|
||||
return response
|
||||
|
||||
async def render(self, templates, request, context=None):
|
||||
context = context or {}
|
||||
template = self.ds.jinja_env.select_template(templates)
|
||||
|
|
@ -81,6 +96,7 @@ class BaseView(AsgiView):
|
|||
**{
|
||||
"database_url": self.database_url,
|
||||
"database_color": self.database_color,
|
||||
"show_messages": lambda: self.ds._show_messages(request),
|
||||
"select_templates": [
|
||||
"{}{}".format(
|
||||
"*" if template_name == template.name else "", template_name
|
||||
|
|
|
|||
|
|
@ -94,3 +94,27 @@ class PermissionsDebugView(BaseView):
|
|||
request,
|
||||
{"permission_checks": reversed(self.ds._permission_checks)},
|
||||
)
|
||||
|
||||
|
||||
class MessagesDebugView(BaseView):
|
||||
name = "messages_debug"
|
||||
|
||||
def __init__(self, datasette):
|
||||
self.ds = datasette
|
||||
|
||||
async def get(self, request):
|
||||
return await self.render(["messages_debug.html"], request)
|
||||
|
||||
async def post(self, request):
|
||||
post = await request.post_vars()
|
||||
message = post.get("message", "")
|
||||
message_type = post.get("message_type") or "INFO"
|
||||
assert message_type in ("INFO", "WARNING", "ERROR", "all")
|
||||
datasette = self.ds
|
||||
if message_type == "all":
|
||||
datasette.add_message(request, message, datasette.INFO)
|
||||
datasette.add_message(request, message, datasette.WARNING)
|
||||
datasette.add_message(request, message, datasette.ERROR)
|
||||
else:
|
||||
datasette.add_message(request, message, getattr(datasette, message_type))
|
||||
return Response.redirect("/")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue