From 3752e12f5bac683e5e2092a6bd209c62c68f5bc3 Mon Sep 17 00:00:00 2001 From: laund Date: Thu, 21 Nov 2024 07:42:38 +0100 Subject: [PATCH] Add new "uses" parameter to ActionFactory methods, allowing additional fixtures to be provided for specific controllers (#946) * Add uses argument to ActionFactory calls for adding fixtures * document ActionFactory better in "convenience decorators" section * fix gitinore for apps folders "It is not possible to re-include a file if a parent directory of that file is excluded." caused the !apps/... re-includes not to work * example convenience decorator comment --------- Co-authored-by: Laurin Schmidt --- .gitignore | 2 +- apps/_scaffold/common.py | 2 ++ apps/fadebook/common.py | 2 ++ apps/tagged_posts/common.py | 2 ++ docs/chapter-06.rst | 51 ++++++++++++++++++++++++++++++++----- py4web/utils/factories.py | 38 +++++++++++++++------------ 6 files changed, 74 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index c2ac47a11..6f3dc9318 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ Thumbs.db ./*.zip apps/_documentation/static/*/*.pdf apps/_documentation/static/*/*.epub -apps*/* +apps*/*/* !apps/todo/* !apps/showcase/* !apps/_dashboard/* diff --git a/apps/_scaffold/common.py b/apps/_scaffold/common.py index ef8ac44e9..acb6570ff 100644 --- a/apps/_scaffold/common.py +++ b/apps/_scaffold/common.py @@ -214,6 +214,8 @@ def download(filename): # Define convenience decorators # They can be used instead of @action and @action.uses # They should NEVER BE MIXED with @action and @action.uses +# If you need to provide extra fixtures for a specific controller +# add them like this: @authenticated(uses=[extra_fixture]) # ####################################################### unauthenticated = ActionFactory(db, session, T, flash, auth) authenticated = ActionFactory(db, session, T, flash, auth.user) diff --git a/apps/fadebook/common.py b/apps/fadebook/common.py index ef8ac44e9..acb6570ff 100644 --- a/apps/fadebook/common.py +++ b/apps/fadebook/common.py @@ -214,6 +214,8 @@ def download(filename): # Define convenience decorators # They can be used instead of @action and @action.uses # They should NEVER BE MIXED with @action and @action.uses +# If you need to provide extra fixtures for a specific controller +# add them like this: @authenticated(uses=[extra_fixture]) # ####################################################### unauthenticated = ActionFactory(db, session, T, flash, auth) authenticated = ActionFactory(db, session, T, flash, auth.user) diff --git a/apps/tagged_posts/common.py b/apps/tagged_posts/common.py index ef8ac44e9..acb6570ff 100644 --- a/apps/tagged_posts/common.py +++ b/apps/tagged_posts/common.py @@ -214,6 +214,8 @@ def download(filename): # Define convenience decorators # They can be used instead of @action and @action.uses # They should NEVER BE MIXED with @action and @action.uses +# If you need to provide extra fixtures for a specific controller +# add them like this: @authenticated(uses=[extra_fixture]) # ####################################################### unauthenticated = ActionFactory(db, session, T, flash, auth) authenticated = ActionFactory(db, session, T, flash, auth.user) diff --git a/docs/chapter-06.rst b/docs/chapter-06.rst index dc1b0dd08..ce7202c1f 100644 --- a/docs/chapter-06.rst +++ b/docs/chapter-06.rst @@ -1122,9 +1122,9 @@ Convenience Decorators ---------------------- The ``_scaffold`` application, in ``common.py`` defines two special -convenience decorators: +convenience decorators using ActionFactory: -:: +.. code:: python @unauthenticated() def index(): @@ -1132,7 +1132,7 @@ convenience decorators: and -:: +.. code:: python @authenticated() def index(): @@ -1146,6 +1146,45 @@ arguments of the action separated by a slash (/). - @unauthenticated does not require the user to be logged in. - @authenticated required the user to be logged in. -They can be combined with (and precede) other ``@action.uses(...)`` but -they should not be combined with ``@action(...)`` because they perform -that function automatically. +.. warning:: + + ActionFactory decorators like these cannot be combined + with @action or @action.uses + +The decorators can be used directly as shown above, which enables +all HTTP methods (GET, POST, PUT, ...) but you can also create separate +controllers for each HTTP method: + +.. code:: python + + @authenticated.get() + def index(): + # only handle GET requests + return dict() + + @authenticated.post(path="index") + def index_form(): + # only handle POST requests + return dict() + +The both decorator and its HTTP method calls have the following arguments: + +- ``path`` overwrites the path built from the function name + with the given string. Does not automatically handle arguments. +- ``template`` specifies the template name, instead of using + the function name. +- ``uses`` specify extra fixtures for this specific controllers. + + +.. code:: python + + @authenticated( + path="test", + template="generic.html", + uses=[Inject(message="Hello World")]) + def example(): + return dict() + +As manual ordering of fixtures isn't possible with ``uses``, +make sure the fixtures define their dependencies. +See: :ref:`Fixtures with dependencies` \ No newline at end of file diff --git a/py4web/utils/factories.py b/py4web/utils/factories.py index 9064d79c2..fe11ccf99 100644 --- a/py4web/utils/factories.py +++ b/py4web/utils/factories.py @@ -22,33 +22,38 @@ class ActionFactory: def __init__(self, *fixtures): self.fixtures = fixtures - def get(self, path=None, template=None): - return self._action_maker("GET", path, template) + def get(self, path=None, template=None, uses=None): + return self._action_maker("GET", path, template, uses) - def post(self, path=None, template=None): - return self._action_maker("POST", path, template) + def post(self, path=None, template=None, uses=None): + return self._action_maker("POST", path, template, uses) - def put(self, path=None, template=None): - return self._action_maker("PUT", path, template) + def put(self, path=None, template=None, uses=None): + return self._action_maker("PUT", path, template, uses) - def delete(self, path=None, template=None): - return self._action_maker("DELETE", path, template) + def delete(self, path=None, template=None, uses=None): + return self._action_maker("DELETE", path, template, uses) - def head(self, path=None, template=None): - return self._action_maker("HEAD", path, template) + def head(self, path=None, template=None, uses=None): + return self._action_maker("HEAD", path, template, uses) def __call__( - self, path=None, template=None, method=["GET", "POST", "PUT", "HEAD", "DELETE"] + self, + path=None, + template=None, + method=["GET", "POST", "PUT", "HEAD", "DELETE"], + uses=None ): - return self._action_maker(method, path, template) + return self._action_maker(method, path, template, uses) - def _action_maker(self, method, path, template): + def _action_maker(self, method, path, template, uses): + uses = uses or [] # handle uses=None def make_action(func, path=path, method=method, template=template): if not path: path = func.__name__ for name in func.__code__.co_varnames[: func.__code__.co_argcount]: path += "/<%s>" % name - fixtures = [f for f in self.fixtures] + fixtures = [*self.fixtures, *uses] if template is None: template = func.__name__ + ".html" if template: @@ -59,8 +64,9 @@ def make_action(func, path=path, method=method, template=template): return make_action - def callback(self, path=None): - return CallbackFactory(path, self.fixtures) + def callback(self, path=None, uses=None): + uses = uses or [] # handle uses=None + return CallbackFactory(path, [*self.fixtures, *uses]) class CallbackFactory: