Skip to content

Commit 63dca61

Browse files
authored
Feature: Row actions (#348)
* Feature: Row actions * Test actions when model is not accessible * Remove unnecessary comments * Improve the docstring * Add Documentation * Update changelog * Update translations * Fix CI
1 parent 2d58ded commit 63dca61

File tree

43 files changed

+1620
-892
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1620
-892
lines changed

docs/api/actions/index.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
:::starlette_admin.actions.action
1+
:::starlette_admin.actions
2+
options:
3+
show_root_heading: true
4+
members:
5+
- action
6+
- row_action
7+
- link_row_action

docs/api/auth-provider/index.md

-2
This file was deleted.

docs/api/auth/index.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
:::starlette_admin.auth
2+
options:
3+
show_root_heading: true
4+
members:
5+
- BaseAuthProvider
6+
- AuthProvider

docs/api/fields/index.md

+34-30
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
1-
:::starlette_admin.fields.BaseField
2-
:::starlette_admin.fields.BooleanField
3-
:::starlette_admin.fields.IntegerField
4-
:::starlette_admin.fields.DecimalField
5-
:::starlette_admin.fields.FloatField
6-
:::starlette_admin.fields.StringField
7-
:::starlette_admin.fields.TextAreaField
8-
:::starlette_admin.fields.TinyMCEEditorField
9-
:::starlette_admin.fields.TagsField
10-
:::starlette_admin.fields.EmailField
11-
:::starlette_admin.fields.URLField
12-
:::starlette_admin.fields.PhoneField
13-
:::starlette_admin.fields.ColorField
14-
:::starlette_admin.fields.PasswordField
15-
:::starlette_admin.fields.EnumField
16-
:::starlette_admin.fields.TimeZoneField
17-
:::starlette_admin.fields.CountryField
18-
:::starlette_admin.fields.CurrencyField
19-
:::starlette_admin.fields.DateTimeField
20-
:::starlette_admin.fields.DateField
21-
:::starlette_admin.fields.TimeField
22-
:::starlette_admin.fields.ArrowField
23-
:::starlette_admin.fields.JSONField
24-
:::starlette_admin.fields.FileField
25-
:::starlette_admin.fields.ImageField
26-
:::starlette_admin.fields.RelationField
27-
:::starlette_admin.fields.HasOne
28-
:::starlette_admin.fields.HasMany
29-
:::starlette_admin.fields.ListField
30-
:::starlette_admin.fields.CollectionField
1+
:::starlette_admin.fields
2+
options:
3+
show_root_heading: true
4+
members:
5+
- BaseField
6+
- BooleanField
7+
- IntegerField
8+
- DecimalField
9+
- FloatField
10+
- StringField
11+
- TextAreaField
12+
- TinyMCEEditorField
13+
- TagsField
14+
- EmailField
15+
- URLField
16+
- PhoneField
17+
- ColorField
18+
- PasswordField
19+
- EnumField
20+
- TimeZoneField
21+
- CountryField
22+
- CurrencyField
23+
- DateTimeField
24+
- DateField
25+
- TimeField
26+
- ArrowField
27+
- JSONField
28+
- FileField
29+
- ImageField
30+
- RelationField
31+
- HasOne
32+
- HasMany
33+
- ListField
34+
- CollectionField

docs/api/views/index.md

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
:::starlette_admin.views.BaseView
2-
:::starlette_admin.views.DropDown
3-
:::starlette_admin.views.Link
4-
:::starlette_admin.views.CustomView
5-
:::starlette_admin.views.BaseModelView
1+
:::starlette_admin.views
2+
options:
3+
show_root_heading: true
4+
members:
5+
- BaseView
6+
- DropDown
7+
- Link
8+
- CustomView
9+
- BaseModelView

docs/changelog/index.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
99

1010
### Added
1111

12+
* Row actions by [@jowilf](https://github.com/jowilf) in [#348](https://github.com/jowilf/starlette-admin/pull/348)
13+
14+
1215
* Add Support for Custom Sortable Field Mapping in SQLAlchemy ModelView by [@jowilf](https://github.com/jowilf)
1316
in [#328](https://github.com/jowilf/starlette-admin/pull/328)
1417

15-
!!! usage
18+
??? usage
19+
1620
```python
1721
class Post(Base):
1822
__tablename__ = "post"
@@ -31,7 +35,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3135

3236
* Add support for datatables [state saving](https://datatables.net/examples/basic_init/state_save.html)
3337

34-
!!! usage
38+
??? usage
39+
3540
```python
3641
class MyModelView(ModelView):
3742
save_state = True

docs/tutorial/actions/index.md

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# Actions
2+
3+
In starlette-admin, actions provide an easy way to interact with your database records and
4+
perform various operations such as mass delete, special mass updates, sending emails, etc.
5+
6+
## Batch Actions
7+
8+
By default, to update an object, you must select it in the list page and update it. This works well for a majority of
9+
use cases. However, if you need to make the same change to many objects at once, this workflow can be quite tedious.
10+
11+
In these cases, you can write a *custom batch action* to bulk update many objects at once.
12+
13+
!!! note
14+
15+
*starlette-admin* add by default an action named `delete` to delete many object at once
16+
17+
To add other batch actions to your [ModelView][starlette_admin.views.BaseModelView], besides the default delete action,
18+
you can define a
19+
function that implements the desired logic and wrap it with the [@action][starlette_admin.actions.action] decorator (
20+
Heavily inspired by Flask-Admin).
21+
22+
!!! warning
23+
24+
The batch action name should be unique within a ModelView.
25+
26+
### Example
27+
28+
```python
29+
from typing import List, Any
30+
31+
from starlette.datastructures import FormData
32+
from starlette.requests import Request
33+
from starlette.responses import RedirectResponse, Response
34+
35+
from starlette_admin import action
36+
from starlette_admin.contrib.sqla import ModelView
37+
from starlette_admin.exceptions import ActionFailed
38+
39+
40+
class ArticleView(ModelView):
41+
actions = ["make_published", "redirect", "delete"] # `delete` function is added by default
42+
43+
@action(
44+
name="make_published",
45+
text="Mark selected articles as published",
46+
confirmation="Are you sure you want to mark selected articles as published ?",
47+
submit_btn_text="Yes, proceed",
48+
submit_btn_class="btn-success",
49+
form="""
50+
<form>
51+
<div class="mt-3">
52+
<input type="text" class="form-control" name="example-text-input" placeholder="Enter value">
53+
</div>
54+
</form>
55+
""",
56+
)
57+
async def make_published_action(self, request: Request, pks: List[Any]) -> str:
58+
# Write your logic here
59+
60+
data: FormData = await request.form()
61+
user_input = data.get("example-text-input")
62+
63+
if ...:
64+
# Display meaningfully error
65+
raise ActionFailed("Sorry, We can't proceed this action now.")
66+
# Display successfully message
67+
return "{} articles were successfully marked as published".format(len(pks))
68+
69+
# For custom response
70+
@action(
71+
name="redirect",
72+
text="Redirect",
73+
custom_response=True,
74+
confirmation="Fill the form",
75+
form='''
76+
<form>
77+
<div class="mt-3">
78+
<input type="text" class="form-control" name="value" placeholder="Enter value">
79+
</div>
80+
</form>
81+
'''
82+
)
83+
async def redirect_action(self, request: Request, pks: List[Any]) -> Response:
84+
data = await request.form()
85+
return RedirectResponse(f"https://example.com/?value={data['value']}")
86+
```
87+
88+
## Row actions
89+
90+
Row actions allow you to perform actions on individual items within a list view.
91+
92+
!!! note
93+
94+
By default, starlette-admin includes three (03) row actions
95+
96+
- `view`: redirects to the item's detail page
97+
- `edit`: redirects to the item's edit page
98+
- `delete`: deletes the selected item
99+
100+
To add other row actions to your [ModelView][starlette_admin.views.BaseModelView], besides the default ones, you can
101+
define a function that implements the desired logic and wrap it with
102+
the [@row_action][starlette_admin.actions.row_action] decorator
103+
104+
For cases where a row action should simply navigate users to a website or internal page, it is preferable to
105+
use the [@link_row_action][starlette_admin.actions.link_row_action] decorator. The key difference is
106+
that `link_row_action`
107+
eliminates the need to call the action API. Instead, the link is included directly in the href attribute of the
108+
generated html element (e.g. `<a href='https://example.com/?pk=4' ...>`).
109+
110+
!!! warning
111+
112+
The row actions (both [@row_action][starlette_admin.actions.row_action]
113+
and [@link_row_action][starlette_admin.actions.link_row_action]) name should be unique within a ModelView.
114+
115+
### Example
116+
117+
```python
118+
from typing import Any
119+
120+
from starlette.datastructures import FormData
121+
from starlette.requests import Request
122+
123+
from starlette_admin._types import RowActionsDisplayType
124+
from starlette_admin.actions import link_row_action, row_action
125+
from starlette_admin.contrib.sqla import ModelView
126+
from starlette_admin.exceptions import ActionFailed
127+
128+
129+
class ArticleView(ModelView):
130+
...
131+
row_actions = ["view", "edit", "go_to_example", "make_published",
132+
"delete"] # edit, view and delete are provided by default
133+
row_actions_display_type = RowActionsDisplayType.ICON_LIST # RowActionsDisplayType.DROPDOWN
134+
135+
@row_action(
136+
name="make_published",
137+
text="Mark as published",
138+
confirmation="Are you sure you want to mark this article as published ?",
139+
icon_class="fas fa-check-circle",
140+
submit_btn_text="Yes, proceed",
141+
submit_btn_class="btn-success",
142+
action_btn_class="btn-info",
143+
form="""
144+
<form>
145+
<div class="mt-3">
146+
<input type="text" class="form-control" name="example-text-input" placeholder="Enter value">
147+
</div>
148+
</form>
149+
""",
150+
)
151+
async def make_published_row_action(self, request: Request, pk: Any) -> str:
152+
# Write your logic here
153+
154+
data: FormData = await request.form()
155+
user_input = data.get("example-text-input")
156+
157+
if ...:
158+
# Display meaningfully error
159+
raise ActionFailed("Sorry, We can't proceed this action now.")
160+
# Display successfully message
161+
return "The article was successfully marked as published"
162+
163+
@link_row_action(
164+
name="go_to_example",
165+
text="Go to example.com",
166+
icon_class="fas fa-arrow-up-right-from-square",
167+
)
168+
def go_to_example_row_action(self, request: Request, pk: Any) -> str:
169+
return f"https://example.com/?pk={pk}"
170+
171+
```
172+
173+
### List rendering
174+
175+
The `RowActionsDisplayType` enum provides options for customizing how row actions are displayed in the list view.
176+
177+
#### RowActionsDisplayType.ICON_LIST
178+
179+
```python hl_lines="5"
180+
from starlette_admin._types import RowActionsDisplayType
181+
182+
183+
class ArticleView(ModelView):
184+
row_actions_display_type = RowActionsDisplayType.ICON_LIST
185+
```
186+
187+
<img width="1262" alt="Screenshot 2023-10-21 at 4 39 58 PM" src="https://github.com/jowilf/starlette-admin/assets/31705179/670371b1-7c28-4106-964f-b7136e7268ea">
188+
189+
#### RowActionsDisplayType.DROPDOWN
190+
191+
```python hl_lines="5"
192+
from starlette_admin._types import RowActionsDisplayType
193+
194+
195+
class ArticleView(ModelView):
196+
row_actions_display_type = RowActionsDisplayType.DROPDOWN
197+
```
198+
199+
<img width="1262" alt="Screenshot 2023-10-21 at 4 48 06 PM" src="https://github.com/jowilf/starlette-admin/assets/31705179/cf58efff-2744-4e4f-8e67-fc24fec8746b">

docs/tutorial/authentication/index.md

+25-4
Original file line numberDiff line numberDiff line change
@@ -225,19 +225,21 @@ class ReportView(CustomView):
225225
When view is inaccessible, it does not appear in menu structure
226226

227227
### For [ModelView][starlette_admin.views.BaseModelView]
228-
In [ModelView][starlette_admin.views.BaseModelView], there is four additional methods you can override
229-
to restrict access to current user.
228+
229+
In [ModelView][starlette_admin.views.BaseModelView], you can override the following methods to restrict access to
230+
the current connected user.
230231

231232
* `can_view_details`: Permission for viewing full details of Items
232233
* `can_create`: Permission for creating new Items
233234
* `can_edit`: Permission for editing Items
234235
* `can_delete`: Permission for deleting Items
235-
* `is_action_allowed`: verify if action with `name` is allowed.
236+
* `is_action_allowed`: verify if the action with `name` is allowed.
237+
* `is_row_action_allowed`: verify if the row action with `name` is allowed.
236238

237239
```python
238240
from starlette_admin.contrib.sqla import ModelView
239241
from starlette.requests import Request
240-
from starlette_admin import action
242+
from starlette_admin import action, row_action
241243

242244
class ArticleView(ModelView):
243245
exclude_fields_from_list = [Article.body]
@@ -259,6 +261,11 @@ class ArticleView(ModelView):
259261
return "action_make_published" in request.state.user["roles"]
260262
return await super().is_action_allowed(request, name)
261263

264+
async def is_row_action_allowed(self, request: Request, name: str) -> bool:
265+
if name == "make_published":
266+
return "row_action_make_published" in request.state.user["roles"]
267+
return await super().is_row_action_allowed(request, name)
268+
262269
@action(
263270
name="make_published",
264271
text="Mark selected articles as published",
@@ -269,4 +276,18 @@ class ArticleView(ModelView):
269276
async def make_published_action(self, request: Request, pks: List[Any]) -> str:
270277
...
271278
return "{} articles were successfully marked as published".format(len(pks))
279+
280+
281+
@row_action(
282+
name="make_published",
283+
text="Mark as published",
284+
confirmation="Are you sure you want to mark this article as published ?",
285+
icon_class="fas fa-check-circle",
286+
submit_btn_text="Yes, proceed",
287+
submit_btn_class="btn-success",
288+
action_btn_class="btn-info",
289+
)
290+
async def make_published_row_action(self, request: Request, pk: Any) -> str:
291+
...
292+
return "The article was successfully marked as published"
272293
```

0 commit comments

Comments
 (0)