Fixes in documentation

This commit is contained in:
Antonio de la Rosa 2025-04-18 01:42:01 +02:00
parent cccebbbca2
commit 4046583d92
9 changed files with 320 additions and 11 deletions

View file

@ -28,6 +28,15 @@ except:
menu=get_menu(config_admin.modules_admin) menu=get_menu(config_admin.modules_admin)
def base_admin(func_view, env, title, **args): 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') 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) 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: else:
redirect(make_url(config.admin_folder)) redirect(make_url(config.admin_folder))

View file

@ -1,5 +1,24 @@
#!/usr/bin/env python3 #!/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 <https://www.gnu.org/licenses/>.
"""
from paramecio.libraries.db import corefields from paramecio.libraries.db import corefields
from paramecio.libraries.db.coreforms import PasswordForm from paramecio.libraries.db.coreforms import PasswordForm
from paramecio.libraries.i18n import I18n from paramecio.libraries.i18n import I18n
@ -9,7 +28,23 @@ from bottle import request, abort
# Need unittest # Need unittest
"""Functions and classes for process forms"""
def pass_values_to_form(post, arr_form, yes_error=True, pass_values=True): 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: if pass_values:
def get_value(key): 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 return arr_form
class CheckForm(): class CheckForm():
"""Simple class for make similar check to pass_values_to_form. More simple.
"""
def __init__(self): def __init__(self):
@ -52,6 +89,20 @@ class CheckForm():
def check(self, post, arr_form): 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(): for k in arr_form.keys():
post[k]=post.get(k, '') post[k]=post.get(k, '')
@ -69,6 +120,21 @@ class CheckForm():
def check_form(post, arr_form, sufix_form='_error'): 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=0
error_form={} 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'): def show_form(post, arr_form, t, yes_error=True, pass_values=True, modelform_tpl='forms/modelform.phtml'):
# Create csrf_token in session # 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() 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) 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 #Simple Function for add repeat_password form to user model
def set_extra_forms_user(user_admin): 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['password'].required=True
user_admin.fields['email'].required=True user_admin.fields['email'].required=True
@ -121,6 +219,15 @@ def ini_fields(fields):
def csrf_token(token_id='csrf_token'): 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() s=get_session()
#if not 'csrf_token' in s: #if not 'csrf_token' in s:
@ -132,6 +239,12 @@ def csrf_token(token_id='csrf_token'):
def generate_csrf(): def generate_csrf():
"""Function for generate a csrf token in a variable
Returns:
csrf_token (str): csrf token value
"""
s=get_session() s=get_session()
if not 'csrf_token' in s: if not 'csrf_token' in s:
@ -142,15 +255,24 @@ def generate_csrf():
def request_type(): def request_type():
"""Simple shortcut for get the request_type"""
return request.environ['REQUEST_METHOD'] return request.environ['REQUEST_METHOD']
def check_csrf(name_csrf_token='csrf_token'): 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() 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): if csrf_token=='' or csrf_token!=request.forms.get(name_csrf_token):
abort(403) abort(403)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# A bottle plugin for send emails if # A bottle plugin for send emails if error
from settings import config from settings import config
from paramecio.libraries.sendmail import SendMail from paramecio.libraries.sendmail import SendMail
@ -12,6 +12,7 @@ if hasattr(config, 'email_failed'):
email_failed=config.email_failed email_failed=config.email_failed
class ErrorReportingPlugin(object): class ErrorReportingPlugin(object):
"""Bottle plugin for """
name = 'error_reporting' name = 'error_reporting'
api = 2 api = 2

View file

@ -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 <https://www.gnu.org/licenses/>.
"""
from paramecio.libraries.lists import SimpleList from paramecio.libraries.lists import SimpleList
from bottle import request from bottle import request
from paramecio.libraries.urls import add_get_parameters, redirect from paramecio.libraries.urls import add_get_parameters, redirect
@ -13,8 +33,35 @@ _=gtext.gettext
class GenerateAdminClass: class GenerateAdminClass:
"""Class for insert, update and list items of a model
"""
def __init__(self, model, url, t): 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_name=''
self.model=model self.model=model
@ -60,6 +107,13 @@ class GenerateAdminClass:
self.text_home=_('Home') self.text_home=_('Home')
def show(self): 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() getpostfiles=GetPostFiles()
@ -197,10 +251,31 @@ class GenerateAdminClass:
""" """
class GenerateConfigClass: class GenerateConfigClass:
"""Class for generate a simple form for simple data for a configuration
"""
def __init__(self, model, url, t): 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 self.model=model
@ -225,6 +300,11 @@ class GenerateConfigClass:
self.text_home=_('Home') self.text_home=_('Home')
def show(self): 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() getpostfiles=GetPostFiles()

View file

@ -229,6 +229,7 @@ class I18n:
@staticmethod @staticmethod
def get_browser_lang(): def get_browser_lang():
"""Method for get the language from user browser"""
return request.headers.get('Accept-Language', 'en-US') return request.headers.get('Accept-Language', 'en-US')

View file

@ -1,5 +1,24 @@
#!/usr/bin/env python3 #!/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 <https://www.gnu.org/licenses/>.
"""
from math import ceil, floor from math import ceil, floor
from paramecio.libraries.urls import add_get_parameters from paramecio.libraries.urls import add_get_parameters
from paramecio.libraries.i18n import I18n, PGetText from paramecio.libraries.i18n import I18n, PGetText
@ -9,11 +28,27 @@ gtext=PGetText(__file__)
_=gtext.gettext _=gtext.gettext
class Pages: class Pages:
"""Simple class for create html pagination code"""
css_class='link_pages' css_class='link_pages'
@staticmethod @staticmethod
def show( begin_page, total_elements, num_elements, link ,initial_num_pages=20, variable='begin_page', label='', func_jscript=''): 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=''; pages='';

View file

@ -6,11 +6,20 @@ from paramecio.libraries.db.webmodel import WebModel
import inspect import inspect
class LoginPlugin(object): class LoginPlugin(object):
"""Plugin for simple login"""
name = 'login' name = 'login'
api = 2 api = 2
def __init__(self, keyword='login', login_var='login', login_url='login'): 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.keyword=keyword
self.login_var=login_var self.login_var=login_var
@ -18,7 +27,11 @@ class LoginPlugin(object):
def setup(self, app): 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: for other in app.plugins:
if not isinstance(other, LoginPlugin): continue if not isinstance(other, LoginPlugin): continue
if other.keyword == self.keyword: if other.keyword == self.keyword:
@ -27,8 +40,19 @@ class LoginPlugin(object):
def apply(self, callback, context): def apply(self, callback, context):
# Test if the original callback accepts a 'login' keyword. """Test if the original callback accepts a 'login' keyword.
# Ignore it if it does not need a login handle.
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 {} conf = context.config.get(self.keyword) or {}
@ -59,6 +83,7 @@ class LoginPlugin(object):
return wrapper return wrapper
class AdminLoginPlugin(LoginPlugin): class AdminLoginPlugin(LoginPlugin):
"""A plugin for admin things. DEPRECATED"""
name = 'adminlogin' name = 'adminlogin'
api = 2 api = 2
@ -69,11 +94,14 @@ class AdminLoginPlugin(LoginPlugin):
class DbPlugin(object): class DbPlugin(object):
"""A Bottle plugin for create a connection to database"""
name = 'db' name = 'db'
api = 2 api = 2
def __init__(self, keyword='db'): def __init__(self, keyword='db'):
"""A Bottle plugin for create a connection to database"""
self.keyword=keyword self.keyword=keyword
@ -88,8 +116,18 @@ class DbPlugin(object):
def apply(self, callback, context): def apply(self, callback, context):
# Test if the original callback accepts a 'db' keyword. """Test if the original callback accepts a 'db' keyword.
# Ignore it if it does not need a login handle.
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 {} conf = context.config.get('db') or {}

View file

@ -18,7 +18,7 @@ class Items(ListItem):
name=corefields.CharField('name') name=corefields.CharField('name')
class ResponseItems: class StandardResponse:
error=corefields.BooleanField('error') error=corefields.BooleanField('error')
message=corefields.CharField('message') message=corefields.CharField('message')
@ -38,3 +38,8 @@ class ResponseItems:
def toJSON(self): def toJSON(self):
return json.dumps( self, default=lambda o: o.__dict__, sort_keys=True, indent=4 ) return json.dumps( self, default=lambda o: o.__dict__, sort_keys=True, indent=4 )
class ResponseItems(StandardResponse):
pass

View file

@ -18,8 +18,10 @@ import inspect
class Session(dict): class Session(dict):
"""Class for create sessions using itsdangerous library"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Class for create sessions using itsdangerous library"""
self.update(*args, **kwargs) self.update(*args, **kwargs)
@ -28,6 +30,15 @@ class Session(dict):
self.safe=None self.safe=None
def __setitem__(self, item, value): 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) super(Session, self).__setitem__(item, value)
self.changed=True self.changed=True
@ -35,6 +46,11 @@ class Session(dict):
# If use redirect or abort, you can save the session before of redirection # If use redirect or abort, you can save the session before of redirection
def save(self): 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: if self.changed:
@ -51,6 +67,8 @@ class Session(dict):
def get_session(): def get_session():
"""Function for get the session from request object from bottle"""
return request.environ.get('session', {}) return request.environ.get('session', {})
def session_plugin(callback): def session_plugin(callback):