diff --git a/parameciofast/libraries/createforms.py b/parameciofast/libraries/createforms.py new file mode 100644 index 0000000..db66d62 --- /dev/null +++ b/parameciofast/libraries/createforms.py @@ -0,0 +1,7 @@ + +def create_form(pmodel, t): + + model=dict(pmodel) + + return t.load_template('modelform.phtml', model=dict(pmodel)) + diff --git a/parameciofast/libraries/db/extrafields/datetimefield.py b/parameciofast/libraries/db/extrafields/datetimefield.py index b33ac12..7a1b251 100644 --- a/parameciofast/libraries/db/extrafields/datetimefield.py +++ b/parameciofast/libraries/db/extrafields/datetimefield.py @@ -49,14 +49,14 @@ class DateTimeField(PhangoField): elif not datetime.obtain_timestamp(value): self.error=True - self.txt_error=self.error_default+' '+value + self.txt_error=self.error_default+' '+str(value) return '0000-00-00 00:00:00' if value==False: self.error=True - self.txt_error=self.error_default+' '+value + self.txt_error=self.error_default+' '+str(value) return '0000-00-00 00:00:00' else: diff --git a/parameciofast/libraries/db/extraforms/dateform.py b/parameciofast/libraries/db/extraforms/dateform.py index 0c7cb32..b00cc22 100644 --- a/parameciofast/libraries/db/extraforms/dateform.py +++ b/parameciofast/libraries/db/extraforms/dateform.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from parameciofast.libraries.db.coreforms import BaseForm -#from parameciofast.libraries.mtemplates import standard_t +from parameciofast.libraries.mtemplates import standard_t from parameciofast.libraries.datetime import format_timedata class DateForm(BaseForm): @@ -11,7 +11,7 @@ class DateForm(BaseForm): super().__init__(name, value) self.yes_time=False - #self.t=standard_t + self.t=standard_t def form(self): diff --git a/parameciofast/libraries/formsutils.py b/parameciofast/libraries/formsutils.py new file mode 100644 index 0000000..c2e6c1b --- /dev/null +++ b/parameciofast/libraries/formsutils.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 + +""" +Parameciofast is a series of wrappers for FastAPI, 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 parameciofast.libraries.db import corefields +from parameciofast.libraries.db.coreforms import PasswordForm +from parameciofast.libraries.i18n import I18n +from parameciofast.libraries.keyutils import create_key_encrypt +#import sqlite3 + +# For tests outing the web framework + +try: + from settings import config +except: + class config: + pass + +""" +framework='flask' + +if hasattr(config, 'framework'): + framework=config.framework + +if framework=='flask': + from flask import session, request, abort +elif framework=='fastapi': + from parameciofast.libraries.sessions import get_session + pass +""" + +# 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 parameciofast.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): + return post[key] + + else: + def get_value(key): + return arr_form[key].default_value + + for key, value in arr_form.items(): + + post[key]=post.get(key, '') + + #if arr_form[key].default_value=='': + arr_form[key].default_value=get_value(key) + + if arr_form[key].field==None: + arr_form[key].field=corefields.CharField(key, 255, required=False) + + # Recheck value if no set error field + if arr_form[key].field.error == None: + arr_form[key].field.check(post[key]) + + #arr_form[key].txt_error="" + + if arr_form[key].required==True and arr_form[key].field.error==True and yes_error==True: + arr_form[key].txt_error=arr_form[key].field.txt_error + + # Reset error on field. + + arr_form[key].field.error=None + + return arr_form + +class CheckForm(): + """Simple class for make similar check to pass_values_to_form. More simple. + """ + + def __init__(self): + + self.error=0 + + 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 parameciofast.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, '') + + if arr_form[k].field==None: + arr_form[k].field=corefields.CharField(k, 255, required=False) + + post[k]=arr_form[k].field.check(post[k]) + arr_form[k].txt_error=arr_form[k].field.txt_error + + if arr_form[k].field.error==True and arr_form[k].required==True: + self.error+=1 + + return post, arr_form + +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={} + + for k in arr_form.keys(): + + post[k]=post.get(k, '') + + if arr_form[k].field==None: + arr_form[k].field=corefields.CharField(k, 255, required=False) + + post[k]=arr_form[k].field.check(post[k]) + arr_form[k].txt_error=arr_form[k].field.txt_error + + if arr_form[k].field.error==True and arr_form[k].required==True: + error_form['#'+k+sufix_form]=arr_form[k].txt_error + error+=1 + + return error, error_form, post, arr_form + +def show_form(post, arr_form, t, session, yes_error=True, pass_values=True, modelform_tpl='forms/modelform.phtml'): + """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 of 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 parameciofast/libraries/templates/forms/modelform.phtml + + Returns: + + template (str): An html string with the generated form. + + """ + + + # Create csrf_token in session + + generate_csrf(session) + + if pass_values==True: + pass_values_to_form(post, arr_form, yes_error, pass_values) + + return t.load_template(modelform_tpl, session=session, forms=arr_form, csrf_token=csrf_token) + +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 + + user_admin.create_forms(['username', 'email', 'password']) + + user_admin.forms['repeat_password']=PasswordForm('repeat_password', '') + + user_admin.forms['repeat_password'].required=True + + user_admin.forms['repeat_password'].label=_('Repeat Password') + +def csrf_token(session, 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 session: + session['csrf_token']=create_key_encrypt() + + return '' + +def generate_csrf(session): + + """Function for generate a csrf token in a variable + + Returns: + csrf_token (str): csrf token value + """ + + if not 'csrf_token' in session: + session['csrf_token']=create_key_encrypt() + + return session['csrf_token'] + +def check_csrf(session, name_csrf_token='csrf_token'): + + csrf_token=session.get('csrf_token', '') + + if csrf_token=='' or csrf_token!=request.form.get(name_csrf_token): + abort(403) + + diff --git a/parameciofast/libraries/mtemplates.py b/parameciofast/libraries/mtemplates.py index 8f1eeb1..857d2b4 100644 --- a/parameciofast/libraries/mtemplates.py +++ b/parameciofast/libraries/mtemplates.py @@ -276,7 +276,7 @@ def add_css_home_local(file_css, module): pass -#env=env_theme(__file__) +env=env_theme(__file__) -#standard_t=PTemplate(env) +standard_t=PTemplate(env) diff --git a/parameciofast/libraries/templates/forms/colorform.phtml b/parameciofast/libraries/templates/forms/colorform.phtml new file mode 100644 index 0000000..bb21e6d --- /dev/null +++ b/parameciofast/libraries/templates/forms/colorform.phtml @@ -0,0 +1,33 @@ +${add_js_home_local('jquery.min.js', 'admin')} +${add_js_home_local('spectrum.js', 'admin')} +${add_css_home_local('spectrum.css', 'admin')} +<% + + +form.default_value=str(hex(int(form.default_value))).replace('0x', '') + +c=len(form.default_value) + +if(c<6): + repeat=6-c + form.default_value=('0'*repeat)+form.default_value + +form.default_value='#'+form.default_value + +%> + + + diff --git a/parameciofast/libraries/templates/forms/dateform.phtml b/parameciofast/libraries/templates/forms/dateform.phtml new file mode 100644 index 0000000..6b6d852 --- /dev/null +++ b/parameciofast/libraries/templates/forms/dateform.phtml @@ -0,0 +1,71 @@ +${add_js('jquery.min.js', 'admin')} + + + + +% if yes_time==True: + + + +% endif + + + diff --git a/parameciofast/libraries/templates/forms/fileform.phtml b/parameciofast/libraries/templates/forms/fileform.phtml new file mode 100644 index 0000000..290f7e6 --- /dev/null +++ b/parameciofast/libraries/templates/forms/fileform.phtml @@ -0,0 +1,12 @@ +<% + + import os + + name_file=os.path.basename(form.default_value) + +%> + +% if name_file: + ${name_file} +% endif + diff --git a/parameciofast/libraries/templates/forms/i18nform.phtml b/parameciofast/libraries/templates/forms/i18nform.phtml new file mode 100644 index 0000000..e0bc53e --- /dev/null +++ b/parameciofast/libraries/templates/forms/i18nform.phtml @@ -0,0 +1,73 @@ +<% + +choose='' + +%> + +<%def name="select_lang(i18n, lang_selected)"> + % if i18n==lang_selected: + <% + return "choose_flag" + %> + % else: + <% + return "no_choose_flag" + %> + % endif + +<%def name="hide_lang(i18n, lang_selected)"> + % if i18n!=lang_selected: + style="display:none;" + % endif + + +% if lang_selected!=None: + % for i18n in arr_i18n: + + ${form.change_name(name_form+'_'+i18n)} + <% + form.default_value=default_value[i18n] + %> + ${form.form()|n} ${name_form}_${i18n} + + % endfor + + +% endif +% if len(arr_i18n)==1: + + + +% endif + + + diff --git a/parameciofast/libraries/templates/forms/modelform.phtml b/parameciofast/libraries/templates/forms/modelform.phtml new file mode 100644 index 0000000..690af9c --- /dev/null +++ b/parameciofast/libraries/templates/forms/modelform.phtml @@ -0,0 +1,27 @@ +<%def name="check_required(required)"> + % if required: + ${'*'} \ + % endif + +<%def name="help(help, name)"> + % if help: + + + % endif + +<%def name="help_tooltip(help, name)"> + % if help: + + % endif + +
+ % for form in forms.values(): + % if form.type!='hidden': +

${form.form()|n} ${form.txt_error}

+ ${help_tooltip(form.help, form.name)} + % else: + ${form.form()|n} + % endif + % endfor + ${csrf_token(session)|n} +
diff --git a/parameciofast/libraries/templates/forms/texthtmlform.phtml b/parameciofast/libraries/templates/forms/texthtmlform.phtml new file mode 100644 index 0000000..0c4cb7f --- /dev/null +++ b/parameciofast/libraries/templates/forms/texthtmlform.phtml @@ -0,0 +1,22 @@ +

+ +

+ +${add_js('tinymce/tinymce.min.js', 'admin')} diff --git a/parameciofast/libraries/templates/utils/edit.phtml b/parameciofast/libraries/templates/utils/edit.phtml new file mode 100644 index 0000000..ba94ffa --- /dev/null +++ b/parameciofast/libraries/templates/utils/edit.phtml @@ -0,0 +1,18 @@ +<%inherit file="../layout.phtml"/> +<%block name="content"> +
+${edit_form|n} +

+

+ +<%block name="jscript_block"> + + diff --git a/parameciofast/modules/fastadmin/__init__.py b/parameciofast/modules/fastadmin/__init__.py index c8eaab1..fe0f601 100644 --- a/parameciofast/modules/fastadmin/__init__.py +++ b/parameciofast/modules/fastadmin/__init__.py @@ -18,6 +18,8 @@ url_app=config.apps['admin'][2] admin_app=FastAPI(docs_url="/docs", openapi_url="/docs/openapi.json", title='Paramecio Admin', version='0.9') +# The plugins are added inverse. + @admin_app.middleware('http') async def db_connect(request: Request, call_next): diff --git a/parameciofast/modules/fastadmin/ausers.py b/parameciofast/modules/fastadmin/ausers.py index 9c616a4..9204d3d 100644 --- a/parameciofast/modules/fastadmin/ausers.py +++ b/parameciofast/modules/fastadmin/ausers.py @@ -10,7 +10,8 @@ from parameciofast.fast import app import os from pydantic import BaseModel from parameciofast.libraries.lists import SimpleList - +from parameciofast.libraries.formsutils import show_form +from parameciofast.libraries.db.coreforms import SelectForm env=env_theme(__file__) @@ -23,7 +24,7 @@ tpl_path=os.path.dirname(__file__).replace('/admin', '')+'/templates/admin' if t.env.directories[1]!=tpl_path: t.env.directories.insert(1, tpl_path) -modules_admin.append(['menu_users', 'people-circle', True]) +#modules_admin.append(['menu_users', 'people-circle', True]) modules_admin.append(['fastadmin_users', 'people-circle']) @@ -53,8 +54,30 @@ def fastadmin_users_edit(item_id: int, request: Request): db=request.state.db - #return t.load_template('edit_users.phtml', title=i18n.tlang('Edit admin users'), tlang=i18n.tlang, url_for=app.url_path_for, module_selected='fastadmin_users', session=request.session) - return "" + session=request.session + + users=UserAdmin(db) + + users.fields['dark_theme'].name_form=SelectForm + users.fields['disabled'].name_form=SelectForm + users.fields['double_auth'].name_form=SelectForm + + users.create_forms() + + users.forms['dark_theme'].arr_select={0: _('Light theme'), 1: _('Dark theme')} + users.forms['disabled'].arr_select={0: _('No'), 1: _('Yes')} + users.forms['double_auth'].arr_select={0: _('No'), 1: _('Yes')} + + arr_user=users.select_a_row(item_id) + + fields=['username', 'password', 'email', 'lang', 'disabled', 'double_auth', 'dark_theme'] + + forms={k:v for k,v in users.forms.items() if k in fields} + + edit_form=show_form(arr_user, forms, t, session, yes_error=False, pass_values=True, modelform_tpl='forms/modelform.phtml') + + return t.load_template('utils/edit.phtml', title=i18n.tlang('Edit admin users'), tlang=i18n.tlang, url_for=app.url_path_for, module_selected='fastadmin_users', session=request.session, edit_form=edit_form) + #return ""