From 4046583d9248500999cd84c843223e78a3084f9d Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Fri, 18 Apr 2025 01:42:01 +0200 Subject: [PATCH] Fixes in documentation --- paramecio/libraries/base_admin.py | 11 +- paramecio/libraries/db/formsutils.py | 126 +++++++++++++++++++- paramecio/libraries/error_reporting.py | 3 +- paramecio/libraries/generate_admin_class.py | 82 ++++++++++++- paramecio/libraries/i18n.py | 1 + paramecio/libraries/pages.py | 35 ++++++ paramecio/libraries/plugins.py | 48 +++++++- paramecio/libraries/responsesapi.py | 7 +- paramecio/libraries/sessionplugin.py | 18 +++ 9 files changed, 320 insertions(+), 11 deletions(-) diff --git a/paramecio/libraries/base_admin.py b/paramecio/libraries/base_admin.py index 4e40cfe..6bdd68d 100644 --- a/paramecio/libraries/base_admin.py +++ b/paramecio/libraries/base_admin.py @@ -28,6 +28,15 @@ except: menu=get_menu(config_admin.modules_admin) def base_admin(func_view, env, title, **args): + """Simple function for make admin sites using template + + Args: + func_view (function): Function that return html code for insert into admin/content.phtml + + Returns: + str: HTML code. + + """ env.directories.insert(1, config.paramecio_root+'/modules/admin/templates') @@ -47,7 +56,7 @@ def base_admin(func_view, env, title, **args): content_index=func_view(connection, t, s, **args) - return t.render_template('admin/content.html', title=title, content_index=content_index, menu=menu, lang_selected=lang_selected, arr_i18n=I18n.dict_i18n) + return t.render_template('admin/content.phtml', title=title, content_index=content_index, menu=menu, lang_selected=lang_selected, arr_i18n=I18n.dict_i18n) else: redirect(make_url(config.admin_folder)) diff --git a/paramecio/libraries/db/formsutils.py b/paramecio/libraries/db/formsutils.py index b72046d..b64d9ed 100644 --- a/paramecio/libraries/db/formsutils.py +++ b/paramecio/libraries/db/formsutils.py @@ -1,5 +1,24 @@ #!/usr/bin/env python3 +""" +Parameciofm is a series of wrappers for bottle, mako and others and construct a simple headless cms. + +Copyright (C) 2024 Antonio de la Rosa Caballero + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + from paramecio.libraries.db import corefields from paramecio.libraries.db.coreforms import PasswordForm from paramecio.libraries.i18n import I18n @@ -9,7 +28,23 @@ from bottle import request, abort # Need unittest +"""Functions and classes for process forms""" + def pass_values_to_form(post, arr_form, yes_error=True, pass_values=True): + """Function for pass a dict with form values for check using forms dict + + Values dict and Forms dict need have the same key. A forms dict is maked of a serie of paramecio2 forms elements, used for check the value. + + Args: + post (dict): Dict composed by a series of values. The keys need to be equal to keys of arr_form dict. + arr_form (dict): Dict composed by a series or forms objects. The keys need to be equal to keys of post dict. + yes_error (bool): Show errors in txt_error form variables. + pass_values (bool): Pass default values or values from post dict to arr_form dict items + + Returns: + arr_form (dict): Return arr_form dict with checked values from post dict. + + """ if pass_values: def get_value(key): @@ -45,6 +80,8 @@ def pass_values_to_form(post, arr_form, yes_error=True, pass_values=True): return arr_form class CheckForm(): + """Simple class for make similar check to pass_values_to_form. More simple. + """ def __init__(self): @@ -52,6 +89,20 @@ class CheckForm(): def check(self, post, arr_form): + """Simple method for pass a dict with form values for check using forms dict + + Values dict and Forms dict need have the same key. A forms dict is maked of a serie of paramecio2 forms elements, used for check the value. + + Args: + post (dict): Dict composed by a series of values. The keys need to be equal to keys of arr_form dict. + arr_form (dict): Dict composed by a series or forms objects. The keys need to be equal to keys of post dict. + + Returns: + post (dict): Return post dict with values checked + arr_form (dict): Return arr_form with errors and values. + + """ + for k in arr_form.keys(): post[k]=post.get(k, '') @@ -69,6 +120,21 @@ class CheckForm(): def check_form(post, arr_form, sufix_form='_error'): + """Function for make check form, passing errors to extra dict called error_form. Also returns an bool variable setting error. + + Args: + post (dict): Dict composed by a series of values. The keys need to be equal to keys of arr_form dict. + arr_form (dict): Dict composed by a series or forms objects. The keys need to be equal to keys of post dict. + sufix_form (str): Define the sufix of error_form keys + + Returns: + error (bool): Return False if not errors in checking, if errors return True + error_form (dict): A dict containing the errors in form fields. + post (dict): Sanitized values + arr_form (dict): arr_form with errors and values. + + """ + error=0 error_form={} @@ -91,6 +157,22 @@ def check_form(post, arr_form, sufix_form='_error'): def show_form(post, arr_form, t, yes_error=True, pass_values=True, modelform_tpl='forms/modelform.phtml'): # Create csrf_token in session + """Function for generate a html form from a template + + Args: + post (dict): Dict composed by a series of values. The keys need to be equal to keys of arr_form dict. + arr_form (dict): Dict composed by a series or forms objects. The keys need to be equal to keys of post dict. + t (PTemplate): Object used for load template for form + yes_error (bool): Show errors in txt_error form variables. + pass_values (bool): Pass default values or values from post dict to arr_form dict items + modelform_tpl (str): Path for the template that generate the html form. By default is paramecio2/libraries/templates/forms/modelform.phtml + + Returns: + + template (str): An html string with the generated form. + + """ + generate_csrf() @@ -99,9 +181,25 @@ def show_form(post, arr_form, t, yes_error=True, pass_values=True, modelform_tpl return t.load_template(modelform_tpl, forms=arr_form) +def extract_post(post, fields): + """Helper function for create a simple array from other using fields list for filter + + Args: + post (dict): A dict with keys and values to filter. + fields (list): A list with keys to validate. + """ + + return {k:v for k,v in post.items() if k in fields} + #Simple Function for add repeat_password form to user model def set_extra_forms_user(user_admin): + """Helper function for add extraforms to UserModel form, not for general use + + Args: + user_admin (UserModel): Instance of UserModel object for modify forms and fields + + """ user_admin.fields['password'].required=True user_admin.fields['email'].required=True @@ -121,6 +219,15 @@ def ini_fields(fields): def csrf_token(token_id='csrf_token'): + """Function for generate a csrf token html hide form using flask sessions + + Args: + token_id (str): Name of the html hide form + + Returns: + html (str): Return html input hidden with csrf token saved in session + """ + s=get_session() #if not 'csrf_token' in s: @@ -132,6 +239,12 @@ def csrf_token(token_id='csrf_token'): def generate_csrf(): + """Function for generate a csrf token in a variable + + Returns: + csrf_token (str): csrf token value + """ + s=get_session() if not 'csrf_token' in s: @@ -142,15 +255,24 @@ def generate_csrf(): def request_type(): + """Simple shortcut for get the request_type""" + return request.environ['REQUEST_METHOD'] def check_csrf(name_csrf_token='csrf_token'): + """Function for check the csrf token + + Args: + name_csrf_token (str): Name of the csrf_token in session + + """ + session=get_session() - csrf_token=session.get('csrf_token', '') + csrf_token=session.get(name_csrf_token, '') if csrf_token=='' or csrf_token!=request.forms.get(name_csrf_token): abort(403) - + diff --git a/paramecio/libraries/error_reporting.py b/paramecio/libraries/error_reporting.py index f6fc2ae..0c1e960 100644 --- a/paramecio/libraries/error_reporting.py +++ b/paramecio/libraries/error_reporting.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# A bottle plugin for send emails if +# A bottle plugin for send emails if error from settings import config from paramecio.libraries.sendmail import SendMail @@ -12,6 +12,7 @@ if hasattr(config, 'email_failed'): email_failed=config.email_failed class ErrorReportingPlugin(object): + """Bottle plugin for """ name = 'error_reporting' api = 2 diff --git a/paramecio/libraries/generate_admin_class.py b/paramecio/libraries/generate_admin_class.py index d2b3f14..e61561c 100644 --- a/paramecio/libraries/generate_admin_class.py +++ b/paramecio/libraries/generate_admin_class.py @@ -1,3 +1,23 @@ + +""" +Paramecio2fm is a series of wrappers for Flask, mako and others and construct a simple headless cms. + +Copyright (C) 2023 Antonio de la Rosa Caballero + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + from paramecio.libraries.lists import SimpleList from bottle import request from paramecio.libraries.urls import add_get_parameters, redirect @@ -13,8 +33,35 @@ _=gtext.gettext class GenerateAdminClass: + """Class for insert, update and list items of a model + """ + def __init__(self, model, url, t): + """A class for generate forms, insert and update items from a database model + + For an easy and fast access to database data, you can use this class for get a simple database model of paramecio and get list of items, add forms, edit forms and more. + + Args: + model (WebModel): A WebModel model (equivalent to database mysql table) + url (str): A string with the base url for the forms. + t (PTemplate): Template used for the class. Normally template subclassed from admin_t PTemplate + + Attributes: + + model (WebModel): The webmodel used for generate the admin model form + t (PTemplate): Template used for the class. Normally template subclassed from admin_t PTemplate + list (SimpleList): A SimpleList class used for generate the listing + arr_fields_edit (list): A list with the fields that the user can edit + url (str): Base url used by GenerateAdminClass for generate edit, insert and other urls. + template_insert (str): The template used for the insert form + template_admin (str): The template used for the base admin site + template_delete (str): The template used for verify delete of an item + url_redirect (str): The url where user is redirect when an operation is done + post_update (function): A Function with item id used how argument for make a post-progressing after update. + + """ + self.model_name='' self.model=model @@ -60,6 +107,13 @@ class GenerateAdminClass: self.text_home=_('Home') def show(self): + """ Method for show the admin model + + Depending of op_admin arg, you have the different sections of a simple administrator + + Returns: + html (str): The html content of the admin page, can be, items list, forms for create items, etc... + """ getpostfiles=GetPostFiles() @@ -197,10 +251,31 @@ class GenerateAdminClass: """ class GenerateConfigClass: + """Class for generate a simple form for simple data for a configuration + """ def __init__(self, model, url, t): + """Class for generate a simple form for simple data for a configuration database model + + You can use this class if you need a simple table for configurations. You create the model and you can generate the configuration instancing this class in your admin + + Args: + model (WebModel): A WebModel model (equivalent to database mysql table) + url (str): A string with the base url for the forms. + t (PTemplate): Template used for the class. Normally template subclassed from admin_t PTemplate + + Attributes: + model (WebModel): The webmodel used for generatre the admin model form + t (PTemplate): Template used for the class. Normally template subclassed from admin_t PTemplate + url (str): Base url used by GenerateConfigClass for different sections of update configuration model + url_redirect (str): The url where user is redirect when an operation is done + arr_fields_edit (list): A list with the fields that the user can edit + template_insert (str): The template used for the insert form + post_update (function): A Function with item id used how argument for make a post-progressing after update. + text_home (str): A str contening the text of home configuration + """ - self.model_name='' + #self.model_name='' self.model=model @@ -225,6 +300,11 @@ class GenerateConfigClass: self.text_home=_('Home') def show(self): + """ Method for show the config admin model + + Depending of op_config arg, you have the different sections of a simple configuration administrator + + """ getpostfiles=GetPostFiles() diff --git a/paramecio/libraries/i18n.py b/paramecio/libraries/i18n.py index 2d40816..af11050 100644 --- a/paramecio/libraries/i18n.py +++ b/paramecio/libraries/i18n.py @@ -229,6 +229,7 @@ class I18n: @staticmethod def get_browser_lang(): + """Method for get the language from user browser""" return request.headers.get('Accept-Language', 'en-US') diff --git a/paramecio/libraries/pages.py b/paramecio/libraries/pages.py index e72def0..c73ce27 100644 --- a/paramecio/libraries/pages.py +++ b/paramecio/libraries/pages.py @@ -1,5 +1,24 @@ #!/usr/bin/env python3 +""" +Paramecio2fm is a series of wrappers for bottle, mako and others and construct a simple headless cms. + +Copyright (C) 2025 Antonio de la Rosa Caballero + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + from math import ceil, floor from paramecio.libraries.urls import add_get_parameters from paramecio.libraries.i18n import I18n, PGetText @@ -9,11 +28,27 @@ gtext=PGetText(__file__) _=gtext.gettext class Pages: + """Simple class for create html pagination code""" css_class='link_pages' @staticmethod def show( begin_page, total_elements, num_elements, link ,initial_num_pages=20, variable='begin_page', label='', func_jscript=''): + """Static method for create the html pagination + + With this method, you can create html pagination code with automated urls for load every page. You can use it also how base for ajax pagination + + Args: + begin_page (int): The number where pagination begin + total_elements (int): The total items in pages + num_elements (int): The number of items for every page + link (str): The url of every page + initial_num_pages (int): Optional. Number of pages showed in pagination, if you have 50 pages, if this value is 20, an interval of 20 pages is showed, with first pages links, and after pages links for navigate between many pages. + variable (str): Optional. The name of GET url variable used for send the first element in the query for get the page. + label (str): Optional. In the future will be used for identify some html tags + func_jscript (str): Javascript function to be executed when page url is clicked. + + """ pages=''; diff --git a/paramecio/libraries/plugins.py b/paramecio/libraries/plugins.py index c5c5756..2bb3272 100644 --- a/paramecio/libraries/plugins.py +++ b/paramecio/libraries/plugins.py @@ -6,11 +6,20 @@ from paramecio.libraries.db.webmodel import WebModel import inspect class LoginPlugin(object): + """Plugin for simple login""" name = 'login' api = 2 def __init__(self, keyword='login', login_var='login', login_url='login'): + """Simple bottle plugin for standard login + + Args: + keyword (str): The variable name for activate the plugin + login_var (str): The name used for define the login variable + login_url (str): The url of the login page + + """ self.keyword=keyword self.login_var=login_var @@ -18,7 +27,11 @@ class LoginPlugin(object): def setup(self, app): - ''' Make sure that other installed plugins don't affect the same keyword argument.''' + """Make sure that other installed plugins don't affect the same keyword argument. + Args: + app (Bottle): The app object used in the request. + + """ for other in app.plugins: if not isinstance(other, LoginPlugin): continue if other.keyword == self.keyword: @@ -27,8 +40,19 @@ class LoginPlugin(object): def apply(self, callback, context): - # Test if the original callback accepts a 'login' keyword. - # Ignore it if it does not need a login handle. + """Test if the original callback accepts a 'login' keyword. + + Ignore it if it does not need a login handle. + + Args: + callback (function): The callback used in the plugin + context (object): An object with the context of function + + Returns: + + function: return the result of the route + + """ conf = context.config.get(self.keyword) or {} @@ -59,6 +83,7 @@ class LoginPlugin(object): return wrapper class AdminLoginPlugin(LoginPlugin): + """A plugin for admin things. DEPRECATED""" name = 'adminlogin' api = 2 @@ -69,11 +94,14 @@ class AdminLoginPlugin(LoginPlugin): class DbPlugin(object): + """A Bottle plugin for create a connection to database""" name = 'db' api = 2 def __init__(self, keyword='db'): + """A Bottle plugin for create a connection to database""" + self.keyword=keyword @@ -88,8 +116,18 @@ class DbPlugin(object): def apply(self, callback, context): - # Test if the original callback accepts a 'db' keyword. - # Ignore it if it does not need a login handle. + """Test if the original callback accepts a 'db' keyword. + + Ignore it if it does not need a login handle. + + Args: + callback (function): The callback used in the plugin + context (object): An object with the context of function + + Returns: + + function: return the result of the route + """ conf = context.config.get('db') or {} diff --git a/paramecio/libraries/responsesapi.py b/paramecio/libraries/responsesapi.py index 17348c2..002ac0f 100644 --- a/paramecio/libraries/responsesapi.py +++ b/paramecio/libraries/responsesapi.py @@ -18,7 +18,7 @@ class Items(ListItem): name=corefields.CharField('name') -class ResponseItems: +class StandardResponse: error=corefields.BooleanField('error') message=corefields.CharField('message') @@ -38,3 +38,8 @@ class ResponseItems: def toJSON(self): return json.dumps( self, default=lambda o: o.__dict__, sort_keys=True, indent=4 ) + + +class ResponseItems(StandardResponse): + pass + diff --git a/paramecio/libraries/sessionplugin.py b/paramecio/libraries/sessionplugin.py index ec63835..11612d9 100644 --- a/paramecio/libraries/sessionplugin.py +++ b/paramecio/libraries/sessionplugin.py @@ -18,8 +18,10 @@ import inspect class Session(dict): + """Class for create sessions using itsdangerous library""" def __init__(self, *args, **kwargs): + """Class for create sessions using itsdangerous library""" self.update(*args, **kwargs) @@ -28,6 +30,15 @@ class Session(dict): self.safe=None def __setitem__(self, item, value): + """Method for set items in session + + With this method, session instance set changed property for know that the session was changed and saved + + Args: + item (str): The key of the session item + value (mixed): The value of the session item + + """ super(Session, self).__setitem__(item, value) self.changed=True @@ -35,6 +46,11 @@ class Session(dict): # If use redirect or abort, you can save the session before of redirection def save(self): + """Method for save the session + + When bottle make a redirect, directly make a raise exception and not save session. With this method you can save the session before the raise. + """ + if self.changed: @@ -51,6 +67,8 @@ class Session(dict): def get_session(): + """Function for get the session from request object from bottle""" + return request.environ.get('session', {}) def session_plugin(callback):