Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion docs/configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ To add custom action on models to the Admin, you can use the `action` decorator.
!!! example

```python
from sqladmin import BaseView, action
from sqladmin import BaseView, action, Flash
from starlette.responses import RedirectResponse

class UserAdmin(ModelView, model=User):
Expand All @@ -494,6 +494,7 @@ To add custom action on models to the Admin, you can use the `action` decorator.
...

referer = request.headers.get("Referer")
Flash.success("Users approved successfully")
if referer:
return RedirectResponse(referer)
else:
Expand All @@ -509,3 +510,52 @@ The available options for `action` are:
- `add_in_list`: A boolean indicating if this action should be available in list page.
- `add_in_detail`: A boolean indicating if this action should be available in detail page.
- `confirmation_message`: A string message that if defined, will open a modal to ask for confirmation before calling the action method.

You can use `Flash` utility class to notify results to the custom action.

#### Flash Utility Class
All methods are class methods and require the request object to access the user session.

1. **Primary Method**: `Flash.flash()`

The general-purpose method for adding any message with an explicitly defined `FlashLevel`.

| Parameter | Type | Default | Description |
|-----------|--------------|-------------------|------------------------------------------|
| `request` | `Request` | | The current incoming request object. |
| `message` | `str` | | The main text content of the message. |
| `level` | `FlashLevel` | `FlashLevel.info` | The severity level. |
| `title` | `str` | `""` | An optional title for the flash message. |

!!! example

```python
from sqladmin import Flash, FlashLevel

# Explicitly setting the level
Flash.flash(request, "A crucial server process has started.", FlashLevel.warning, "System Alert")
```

2. **Convenience Methods (Shortcuts)**

These methods simplify message creation by automatically setting the appropriate `FlashLevel`.
They accept the same `request`, `message`, and optional `title` parameters.

| Method | Level Set | Description |
|---------------------------------------------|----------------------|--------------------------------------------------|
| `Flash.info(request, message, title="")` | `FlashLevel.info` | Adds a general information message. |
| `Flash.error(request, message, title="")` | `FlashLevel.error` | Adds a high-severity error message. |
| `Flash.warning(request, message, title="")` | `FlashLevel.warning` | Adds a cautionary message. |
| `Flash.success(request, message, title="")` | `FlashLevel.success` | Adds a message confirming successful completion. |

!!! example

```python
from sqladmin import Flash, FlashLevel

# Using the success shortcut
Flash.success(request, "Your profile was updated successfully.", "Update Complete")

# Using the error shortcut
Flash.error(request, "Access denied. Invalid credentials provided.")
```
2 changes: 2 additions & 0 deletions sqladmin/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from sqladmin._types import ENGINE_TYPE
from sqladmin.ajax import QueryAjaxModelLoader
from sqladmin.authentication import AuthenticationBackend, login_required
from sqladmin.flash import get_flashed_messages
from sqladmin.forms import WTFORMS_ATTRS, WTFORMS_ATTRS_REVERSED
from sqladmin.helpers import (
get_object_identifier,
Expand Down Expand Up @@ -115,6 +116,7 @@ def init_templating_engine(self) -> Jinja2Templates:
templates.env.globals["admin"] = self
templates.env.globals["is_list"] = lambda x: isinstance(x, list)
templates.env.globals["get_object_identifier"] = get_object_identifier
templates.env.globals["get_flashed_messages"] = get_flashed_messages

return templates

Expand Down
146 changes: 146 additions & 0 deletions sqladmin/flash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
from enum import Enum
from typing import Dict, List

from starlette.requests import Request


class FlashLevel(Enum):
"""
Defines the standard severity levels for flash messages.
These values are typically used as CSS classes or categories.
"""

info = "primary"
error = "danger"
warning = "warning"
success = "success"


class Flash:
"""
A utility class providing convenient class methods for creating
session-based flash messages with predefined severity levels.
"""

@classmethod
def flash(
cls,
request: Request,
message: str,
level: FlashLevel = FlashLevel.info,
title: str = "",
) -> bool:
"""
Adds a custom flash message in any custom level.

Args:
request: The incoming request object.
message: The message content.
level: The custom flash level.
title: An optional title.
"""
return flash(
request,
message,
level.value,
title,
)

@classmethod
def info(cls, request: Request, message: str, title: str = "") -> bool:
"""
Adds an informational flash message (level: INFO).

Args:
request: The incoming request object.
message: The message content.
title: An optional title.
"""
return cls.flash(
request,
message,
FlashLevel.info,
title,
)

@classmethod
def error(cls, request: Request, message: str, title: str = "") -> bool:
"""
Adds an error flash message (level: ERROR).

Args:
request: The incoming request object.
message: The message content.
title: An optional title.
"""
return cls.flash(
request,
message,
FlashLevel.error,
title,
)

@classmethod
def warning(cls, request: Request, message: str, title: str = "") -> bool:
"""
Adds a warning flash message (level: WARNING).

Args:
request: The incoming request object.
message: The message content.
title: An optional title.
"""
return cls.flash(
request,
message,
FlashLevel.warning,
title,
)

@classmethod
def success(cls, request: Request, message: str, title: str = "") -> bool:
"""
Adds a successful action flash message (level: SUCCESS).

Args:
request: The incoming request object.
message: The message content.
title: An optional title.
"""
return cls.flash(
request,
message,
FlashLevel.success,
title,
)


def get_flashed_messages(request: Request) -> List[Dict[str, str]]:
messages: List[Dict[str, str]] = []
if "session" not in request.scope:
return messages

if "_messages" in request.session:
messages = request.session.pop("_messages")

return messages


def flash(
request: Request, message: str, category: str = "primary", title: str = ""
) -> bool:
if "session" not in request.scope:
return False

if "_messages" not in request.session:
request.session["_messages"] = []

request.session["_messages"].append(
{
"category": category,
"title": title,
"message": message,
}
)

return True
28 changes: 28 additions & 0 deletions sqladmin/templates/sqladmin/flash.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% with messages = get_flashed_messages(request) %}
{% if messages %}
<div class="toast-container position-fixed bottom-0 end-0 p-3">
{% for message in messages %}
<div class="toast text-bg-{{message.category}} " role="alert"
aria-live="assertive" aria-atomic="true"
data-bs-toggle="toast" style="width: auto">
<div class="d-flex">
{% if message.title %}
<div class="toast-header">{{ message.title }}</div>
{% endif %}
<div class="toast-body">{{ message.message }}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
{% endfor %}
</div>
<script >
document.addEventListener('DOMContentLoaded', function() {
let toasts = document.querySelectorAll('.toast');
toasts.forEach(function(toastEl) {
let toast = new bootstrap.Toast(toastEl, {"delay":3000});
toast.show()
})
})
</script>
{% endif %}
{% endwith %}
3 changes: 3 additions & 0 deletions sqladmin/templates/sqladmin/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ <h2 class="page-title">{{ title }}</h2>
<div class="container-fluid">
<div class="row row-deck row-cards">
{% block content %} {% endblock %}
{% block flash %}
{% include 'sqladmin/flash.html' %}
{% endblock %}
</div>
</div>
</div>
Expand Down
Loading