Problems with grids in latest master

165 views
Skip to first unread message

David Manns

unread,
May 30, 2025, 8:04:14 AM5/30/25
to py4web
My app is working fine (I think) with v1.20250524.1, but with latest master all my grids are causing failures.

One simple grid with no search_form produces:
Traceback (most recent call last): File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/core.py", line 1053, in wrapper ret = func(*func_args, **func_kwargs) File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/core.py", line 1038, in wrapper raise exception File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/core.py", line 1014, in wrapper context["output"] = func(*args, **kwargs) File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/apps/oxcam/session.py", line 47, in wrapped_f return f(*args, **kwds) File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/apps/oxcam/controllers.py", line 1966, in accounting grid = Grid(path, db.Bank_Accounts.id>0, File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/utils/grid.py", line 503, in __init__ if isinstance(query, query._db.Table): AttributeError: 'str' object has no attribute '_db'

a more complex grid with a search_form produces:
Traceback (most recent call last): File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/core.py", line 1053, in wrapper ret = func(*func_args, **func_kwargs) File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/core.py", line 1038, in wrapper raise exception File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/py4web/core.py", line 1014, in wrapper context["output"] = func(*args, **kwargs) File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/apps/oxcam/session.py", line 47, in wrapped_f return f(*args, **kwds) File "/Users/davidmanns/Library/CloudStorage/OneDrive-Personal/Desktop/py4web/apps/oxcam/controllers.py", line 290, in members grid = Grid(path, eval(query), left=eval(left) if left else None, TypeError: Grid.__init__() got multiple values for argument 'search_form'

Sorry I don't have time right now to drill down further.

David Manns

unread,
May 30, 2025, 8:18:25 AM5/30/25
to py4web
perhaps the issue is with dependencies?

My development environment is in vscode. For a long time I have updated py4web by doing 'git pull' at the terminal and when there was a requirements.txt file I could use that to update necessary modules in my venv as needed.

Massimo

unread,
May 31, 2025, 7:07:05 PM5/31/25
to py4web
Can you try pip install -r requirements.txt ? You do need the latest pydal. Perhaps that's it, perhaps not.
I am planning to release a new version later today or tomorrow and if, not, I will fix it.

Massimo

unread,
May 31, 2025, 8:52:47 PM5/31/25
to py4web
I read the error again and I understand the problem. The Grid api has changed and you have to modify your apps accordingly

Grid(....). There is no path any more, remove path Grid(...), from the action parameters, and from the routes. For example:

@action("something")
@action("something/<path:path>")
@action.uses(....)
def something(path):
       ... Grid(path, ....) ....

becomes:

@action("something")
@action.uses(....)
def something():
       ... Grid(....) ....

David Manns

unread,
Jun 1, 2025, 9:08:29 AM6/1/25
to py4web
In the latest master I don't see any change to the documentation of Grid. Doc's still show 'path' in the grid signature.

This will have a major impact in my project, I use 'path' a lot in my logic. To give a simple illustration:

I have a Grid controller that's used in making an event booking. A booking consists of multiple table records, one for each guest in a party. Thus there is a grid showing one row for each guest. The initial call uses path 'new' to create the first record. When the grid is displayed (rather than a crud form), i.e. path 'select', and addition checkout form is displayed below the 'grid' which, when submitted, leads to payment collection etc.

Assuming that Grid will still display either a grid or a crud form, there must be a way in the request to distinguish the various cases. Does path change from an argument to a parameter in the request?? Is there any documentation anywhere describing the purpose and details of the refactoring of grid??

Thanks!

David Manns

unread,
Jun 1, 2025, 9:30:51 AM6/1/25
to py4web
I see the post which provides some of the answers. As I noted in the last update to this post, I do use URL's that lead to straight to the inner functions of the grid, so would hate to see that prevented. The change to using parameters in the URL will require quite a bit of work but would be fairly straightforward and the new api is, in principle, cleaner.

As to the documentation, the existing documentation was never really complete, I remember I had to essentially reverse engineer the grid to figure out how to use it, it should be updated to document the request parameters fully.

Provided there is no loss of existing functionality, the refactor seems positive in the long run.

As to the natural language search, that could be interesting, but I hope it would be 'opt-in' and that the existing search_form and search_queries functionality not be dropped. For my project, I probably wouldn't use natural language search.

David
Message has been deleted

Massimo DiPierro

unread,
Jun 1, 2025, 11:39:36 AM6/1/25
to David Manns, py4web
I apologize if this creates inconvenience but I think it is better in the long run. 

Indeed you can still do everything that you want to do. 

Let me know how I can help with the transition. 

Notice the full text search is opt out. I will add it to the docs

--
You received this message because you are subscribed to the Google Groups "py4web" group.
To unsubscribe from this group and stop receiving emails from it, send an email to py4web+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/py4web/7c01a8bc-97d9-4dcd-87dc-0eacf6095d1bn%40googlegroups.com.

Massimo

unread,
Jun 1, 2025, 12:24:05 PM6/1/25
to py4web
You can disable the full text search with

Grid(..., search_queries=[])

Notice you can do:

from my4web.utils.grid import Grid as NewGrid
class Grid(NewGrid):
     def __init__(self, _, query, **kwargs):
            kwargs["search_queries"] = []
            NewGrid.__init__(self, query, **kwargs)

and then use Grid and it will have the same API as before but not the same URLs.

Notice I never promised the generated urls are backward compatible and they may change again as features are added. We could solve this by giving the grid an API to generate the URLs. For example

Grid.link(URL("page-with-grid"), "edit", record_id)

But I am not sure because I would like to give the grid the ability to handle multiple tables and follow references and the number of parameters required for that function would also change.

Massimo

Massimo

unread,
Jun 1, 2025, 2:00:12 PM6/1/25
to py4web
Here is an update to Jim's grid_tutorial to reflect the changes in the new Grid

I was unable to update the HTMX examples because the the "advanced_htmx" action is missing in master. Perhaps Jim can advice.

David Manns

unread,
Jun 4, 2025, 3:59:49 PM6/4/25
to py4web
I'm happy to make the changes to conform to the new Grid, I think its much cleaner to pass parameters to the inner function rather than arguments. And 'referrer' is a good way to pass return context to the inner functions. Please can we document the links to inner functions and how to create/unpack referrer values. And please can we commit to not changing again in future!

To illustrate the kind of situation where calls to the inner functions of other grids are natural, for example, in my app I have 3 tables:

Members
Events
Reservations

Reservations reference both Members and Events. If a Member brings a guest there will be two Reservations records referencing the Member, one marked as host, the other with details of the guest and not marked as host. There are corresponding grid pages:

members
events
event_reservations/event_id      (rows are attending members, guests are not shown, just the party size)
manage_reservation/member_id/event_id    (shows and individual member reservation, rows are member, guests)

In event_reservations there is a Column which shows the member name as a link which goes to manage_reservation/member_id/event_id

In manage_reservation there is a link to drill down on the member details. It is natural to want to be able to drill down in this way. The link (with new grid) to members?mode=edit&id=998&referrer=...

I generate the referrer as
referrer=encode_url(request.url)

I tend to have explicit 'back' links at the top of my pages. For dealing with 'referrer' I have functions
#encode something, usually a URL, for use in another URL, e.g. as referrer
def encode_url(url):
return base64.b16encode(url.encode("utf8")).decode("utf8")

#decode encoded something, usually URL, from a referrer parameter
def decode_url(code):
return base64.b16decode(code.encode("utf8")).decode("utf8")


So in a grid controller like members() will contain something like

mode = request.query.get('mode')
...
if mode:
    back = decode_url(request.query.referrer)
    ...
else:
    back = request.url

'back' is passed as a parameter to any auxiliary pages to allow return to the same context.

Massimo

unread,
Jun 5, 2025, 1:48:45 AM6/5/25
to py4web
Since I cannot promise the backward compatibility of the Grid URL format, I created the following API:

parsed = Grid.parse(request.query)
mode = parsed["mode"] # select, details, edit, delete
record_id = parsed["record_id"] # not null in details, edit, delete
referrer = parsed["referrer"] # can be null

Massimo

unread,
Jun 5, 2025, 1:50:25 AM6/5/25
to py4web
Do not use mode = request.query.get('mode') because mode can be omitted from the query. For example at this time

null => mode=select
?id=3 => mode=details & record_id=3
?id=3&mode=edit => mode=edit & record_id=3
etc.


Tom Clerckx

unread,
Jun 5, 2025, 5:39:29 AM6/5/25
to py4web
Note on the grid_tutorial: https://github.com/jpsteil/grid_tutorial/pull/5 

When running, it will give an error on:
  File "/odindrive/temp/2025-04-23-py4web-migration/test/apps/grid_tutorial/controllers.py", line 4, in <module>
    from py4web.utils.grid import Grid, Column, ActionButton

To overcome, I copied the GridActionButton class from the py4web documentation as a new ActionButton class in grid_tutorial/controllers.py

Tom.

Tom Clerckx

unread,
Jun 5, 2025, 12:19:43 PM6/5/25
to py4web
Something still seems to go wrong with the ActionButton ....
Instead of rendering the button, I get:
ActionButtonIssue.png

Massimo DiPierro

unread,
Jun 5, 2025, 1:00:28 PM6/5/25
to Tom Clerckx, py4web

Yes. There should be no ActionButton any more, just a dict with button text and url


David Manns

unread,
Jun 6, 2025, 9:06:00 AM6/6/25
to py4web
The addition of Grid.parser() can certainly help to insulate Grid controllers from potential future changes. How about a Grid_URL() function to generate URL's for calling inner Grid functions from elsewhere?

David

Tom Clerckx

unread,
Jun 6, 2025, 10:32:57 AM6/6/25
to py4web
Patch for the current issue with the grid_tutorial (since the ActionButton class was removed in py4web)


diff --git a/controllers.py b/controllers.py
index bddcd3a..13ff99a 100644
--- a/controllers.py
+++ b/controllers.py
@@ -1,10 +1,11 @@
from yatl import XML
from py4web import action, URL, request
-from py4web.utils.grid import Grid, Column, ActionButton
+from py4web.utils.grid import Grid, Column
from .common import unauthenticated, session, db, GRID_DEFAULTS
from .grid_helpers import GridSearchQuery, GridSearch
from pydal.validators import IS_NULL_OR, IS_IN_DB, IS_IN_SET
+from yatl.helpers import A, I
@unauthenticated("index", "index.html")
@@ -175,6 +176,19 @@ def can_user_access(action, group_number):
return True
return False
+def reorder_button(row):
+ if row.in_stock > row.reorder_level:
+ return None
+ button = A(
+ I(_class="fas fa-redo"),
+ _href=URL(f"reorder/{row.id}"),
+ _role="button",
+ _title=f"Reorder {row.name}",
+ _message=f"Do you want to reorder {row.name}?",
+ _class="button grid-button is-small",
+ )
+ button.append(XML("&nbsp;Reorder"))
+ return button
@action("action_buttons")
@action.uses(
@@ -184,16 +198,7 @@ def can_user_access(action, group_number):
)
def action_buttons():
pre_action_buttons = [
- lambda row: (
- ActionButton(
- text=f"Reorder {row.name}",
- url=URL("reorder/{row_id}"),
- icon="fa-redo",
- message=f"Do you want to reorder {row.name}?",
- )
- )
- if row.in_stock <= row.reorder_level
- else None
+ lambda row: reorder_button(row),
]
grid = Grid(

Massimo

unread,
Jun 7, 2025, 1:03:03 AM6/7/25
to py4web
Good solution!
Reply all
Reply to author
Forward
0 new messages