diff --git a/parameciofast/console.py b/parameciofast/console.py
new file mode 100644
index 0000000..47dd77c
--- /dev/null
+++ b/parameciofast/console.py
@@ -0,0 +1,412 @@
+#!/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 .
+"""
+
+import argparse
+import os
+import shutil
+import getpass
+import re
+from pathlib import Path
+from base64 import b64encode
+from parameciofast.libraries.db.webmodel import WebModel
+from parameciofast.modules.fastadmin.models.admin import UserAdmin, LoginTries
+from subprocess import call
+from urllib.parse import urlparse
+
+def start():
+
+ parser=argparse.ArgumentParser(prog='parameciofast', description='A tool for create new paramecio2 sites')
+
+ parser.add_argument('--path', help='The path where the paramecio site is located', required=True)
+
+ parser.add_argument('--modules', help='A list separated by commas with the git repos for download modules for this site', required=False)
+
+ parser.add_argument('--symlink', help='Set if create direct symlink to paramecio in new site', action='store_true')
+
+ #parser.add_argument('--tests', help='Create a symlink to tests for check into paramecio site', action='store_true')
+
+ # Options for deploy
+
+ parser.add_argument('--url', help='The http/https base url of the real proxy server. Example: https://www.exampledomain.com, default is http://localhost:5000', required=False)
+
+ parser.add_argument('--folder', help='If you deploy in a subdirectory, set it, without beggining and ending slashes', required=False)
+
+ #parser.add_argument('--host', help='The host ip or domain where the app is binded', required=False)
+
+ #parser.add_argument('--port', help='Change the default port 8080 to other number. Use 80 is not recommended, use 80 for the proxy server how nginx or apache', required=False)
+
+ args=parser.parse_args()
+
+ #print(args)
+ #exit(0)
+
+ workdir=os.path.dirname(os.path.abspath(__file__))
+
+ # Create directory
+
+ path=Path(args.path)
+
+ try:
+ path.mkdir(0o755, True)
+
+ except:
+
+ print('Error: cannot create the directory. Check if exists and if you have permissions')
+ exit()
+ # Create folder settings and copy app.py, admin.py
+
+ path_settings=args.path+'/settings'
+
+ try:
+
+ os.mkdir(path_settings, 0o755)
+ except:
+ print('Error: cannot create the directory. Check if exists and if you have permissions')
+
+ # Copy the files. Need optimization, use an array for save the filenames and a simple for loop.
+
+ try:
+
+ shutil.copy(workdir+'/settings/config.py.sample', path_settings+'/config.py')
+
+ except:
+
+ print('Error: cannot copy the file config.py. Check if exists and if you have permissions for this task')
+
+ try:
+
+ shutil.copy(workdir+'/frontend/app.py', args.path+'/app.py')
+
+ except:
+
+ print('Error: cannot copy the file index.py to app.py. Check if exists and if you have permissions for this task')
+
+
+ if args.symlink==True:
+ try:
+ os.symlink(workdir, args.path+'/paramecio2', True)
+
+ except:
+ print('Error: cannot symlink paramecio in new site')
+
+ """
+ if args.tests==True:
+ try:
+ os.symlink(workdir, args.path+'/paramecio2/', True)
+
+ except:
+ print('Error: cannot symlink paramecio2 in new site')
+ """
+
+ with open(path_settings+'/config.py', 'r') as f:
+ conf=f.read()
+
+ random_bytes = os.urandom(24)
+ secret_key_session = b64encode(random_bytes).decode('utf-8').strip()
+
+ conf=conf.replace('im smoking fool', secret_key_session)
+
+ #domain='localhost'
+
+ #conf=conf.replace("domain='localhost'", "domain='"+args.url+"'")
+ """
+ if args.host==None:
+ args.host='localhost'
+
+ conf=conf.replace("host='localhost'", "host='"+args.host+"'")
+
+ if args.port==None:
+ args.port='8080'
+
+
+ conf=conf.replace("port=8080", "port="+args.port)
+ """
+
+ base_url='/'
+
+ if args.folder==None:
+ args.folder=''
+ else:
+ #args.folder='/'+args.folder
+ base_url='/'+args.folder+'/'
+
+ conf=conf.replace("application_root='/'", "application_root='"+base_url+"'")
+
+ if args.url==None:
+ args.url='http://localhost:5000'
+
+ domain_url=args.url
+
+ conf=conf.replace("domain_url='http://localhost:5000'", "domain_url='"+domain_url+"'")
+
+ # Question about email
+
+ e_q=input('Email for site: ')
+
+ conf=conf.replace("no-reply@example.com", e_q)
+
+ #if e_q=='':
+
+
+ #domain_url='http://localhost:8080'
+
+ with open(path_settings+'/config.py', 'w') as f:
+ f.write(conf)
+
+ # Question about mysql configuration? If yes, install configuration
+
+ s=input('Do you want use paramecio with MySQL database? y/n: ')
+
+ if s=='y' or s=='Y':
+
+ host_db=input('MySQL database server host, by default localhost: ').strip()
+
+ db=input('MySQL database name, by default paramecio_db: ').strip()
+
+ user_db=input('MySQL database user, by default root: ').strip()
+
+ pass_db=getpass.getpass('MySQL database password, by default "": ').strip()
+
+ if host_db=='':
+
+ host_db='localhost'
+
+ if user_db=='':
+
+ user_db='root'
+
+ #user=UserAdmin()
+
+ #Create db
+
+ if db=="":
+ db='paramecio_db'
+
+ WebModel.connections={'default': {'name': 'default', 'host': host_db, 'user': user_db, 'password': pass_db, 'db': '', 'charset': 'utf8mb4', 'set_connection': False, 'db_type': 'pymysql'} }
+
+ connection_code="WebModel.connections={'default': {'name': 'default', 'host': '"+host_db+"', 'user': '"+user_db+"', 'password': '"+pass_db+"', 'db': '"+db+"', 'charset': 'utf8mb4', 'set_connection': False, 'db_type': 'pymysql'} }"
+
+ with open(path_settings+'/config.py', 'a') as f:
+ f.write("\n\n"+connection_code)
+ f.close()
+
+ sql='create database '+db
+
+ conn=WebModel.connection()
+
+ useradmin=UserAdmin(conn)
+ logintries=LoginTries(conn)
+
+ # Check if db exists
+
+ c=0
+
+ with useradmin.query('SHOW DATABASES LIKE "%s"' % db) as cur:
+ c=cur.rowcount
+
+ if c==0:
+ useradmin.query(sql)
+ #print('Error: cannot create database or db doesn\'t exists, check database permissions for this user')
+
+ #if not useradmin.query(sql):
+ #print('Error: cannot create database, check the data of database')
+
+
+ #else:
+
+ useradmin.query('use '+db)
+
+ admin=input('Do you want create admin site? y/n: ')
+
+ if admin=='y' or admin=='Y':
+
+ try:
+
+ #shutil.copy(workdir+'/settings/modules.py.admin', path_settings+'/modules.py')
+
+ #shutil.copy(workdir+'/settings/config_admin.py.sample', path_settings+'/config_admin.py')
+
+ sql=useradmin.create_table()
+
+ if not useradmin.query(sql):
+ print('Error: cannot create table admin, you can create this table with padmin.py')
+ else:
+
+ sql_login=logintries.create_table()
+ logintries.query(sql_login)
+
+ # Add admin module to config
+ with open(path_settings+'/config.py', 'r') as f:
+
+ config_text=f.read()
+
+ f.close()
+
+ #apps={'welcome': ['paramecio2.modules.welcome', 'welcome_app', '/'], 'admin': ['paramecio2.modules.admin', 'admin_app', '/']}
+ """
+ config_text=config_text.replace("apps={'welcome': ['parameciofast.modules.welcome', 'welcome_app', '/']}", "apps={'welcome': ['paramecio2.modules.welcome', 'welcome_app', '/'], 'admin': ['paramecio2.modules.admin', 'admin_app', '/']}")
+
+ with open(path_settings+'/config.py', 'w') as f:
+
+ f.write(config_text)
+
+ f.close()
+
+ try:
+
+ shutil.copy(workdir+'/settings/modules.py.admin', path_settings+'/modules.py')
+
+ except:
+
+ print('Error: cannot copy the file modules.py. Check if exists and if you have permissions for this task')
+ """
+
+ print('Created admin site...')
+
+ except:
+
+ print('Error: cannot create the database. Check if tables exists in it and if you have permissions for this task')
+ exit(1)
+
+ pass
+
+ # Install modules
+
+ if args.modules!=None:
+
+ if args.modules.strip()!='':
+
+ arr_modules=args.modules.split(',')
+
+ final_modules=[]
+
+ final_modules_models=[]
+
+ if len(arr_modules)>0:
+
+ for k, module in enumerate(arr_modules):
+
+ module=module.strip()
+
+ try:
+
+ u=urlparse(module)
+
+ module_path=os.path.basename(u.path)
+
+ except:
+ print('Error: not valid url for repository')
+ exit(1)
+
+
+ if call("git clone %s %s/modules/%s" % (module, path, module_path), shell=True) > 0:
+ print('Error, cannot install the module %s' % module_path)
+ exit(1)
+ else:
+ print('Added module %s' % module_path)
+
+ final_modules.append(("modules/%s" % (module_path)).replace('/', '.'))
+ final_modules_models.append("modules/%s" % (module_path))
+
+
+
+ # Edit config.py
+
+ with open(path_settings+'/config.py') as f:
+
+ modules_final='\''+'\', \''.join(final_modules)+'\''
+
+ p=re.compile(r"^modules=\[(.*)\]$")
+
+ #config_file=p.sub(r"modules=[\1, "+modules_final+"]", "modules=['paramecio.modules.welcome', 'paramecio.modules.admin', 'paramecio.modules.lang', 'modules.pastafari', 'modules.monit', 'modules.example']")
+
+ final_config=''
+
+ for line in f:
+ if p.match(line):
+ line=p.sub(r"modules=[\1, "+modules_final+"]", line)
+ final_config+=line
+
+ with open(path_settings+'/config.py', 'w') as f:
+
+ f.write(final_config)
+
+ print('Updated configuration for add new modules...')
+
+ #Change workdir
+
+ real_dir=os.getcwd()
+
+ os.chdir(args.path)
+
+ # Installing models
+
+ padmin='parameciofastdb'
+
+ os.chmod(padmin, 0o755)
+
+ for mod_path in final_modules_models:
+
+ models_path=mod_path+'/models'
+
+ if os.path.isdir(models_path):
+
+ models_files=os.listdir(models_path)
+
+ m=re.compile(r".*\.py$")
+
+ underscore=re.compile("^__.*")
+
+ for f in models_files:
+
+ if m.match(f) and not underscore.match(f):
+
+ if call('parameciofastdb --model '+models_path+'/'+f, shell=True) > 0:
+ print('Error, cannot create the modules of '+models_path+'/'+f)
+ else:
+ print('Models from '+models_path+'/'+f+' created')
+
+ # Execute two times the loop because i can need good installed models for postscript script
+
+ # Execute postscript
+
+ print('Executing postscripts')
+
+ for mod_path in final_modules_models:
+
+ postscript=mod_path+"/install/postinstall.py"
+
+ os.chmod(padmin, 0o755)
+
+ if os.path.isfile(postscript):
+
+ os.chmod(postscript, 0o755)
+
+ if call('./'+postscript, shell=True) > 0:
+ print('Error, cannot execute the postinstall script')
+ exit(1)
+ else:
+ print('Postinstall script finished')
+
+ conn.close()
+
+if __name__=="__main__":
+ start()
diff --git a/parameciofast/frontend/app.py b/parameciofast/frontend/app.py
new file mode 100644
index 0000000..faf4e65
--- /dev/null
+++ b/parameciofast/frontend/app.py
@@ -0,0 +1,4 @@
+
+from parameciofast.fast import app
+
+
diff --git a/parameciofast/libraries/templates/modelform.phtml b/parameciofast/libraries/templates/modelform.phtml
new file mode 100644
index 0000000..b6f9deb
--- /dev/null
+++ b/parameciofast/libraries/templates/modelform.phtml
@@ -0,0 +1,28 @@
+<%def name="check_required(required)">
+ % if required:
+ ${'*'} \
+ % endif
+%def>
+<%def name="help(help, name)">
+ % if help:
+
+
+ % endif
+%def>
+<%def name="help_tooltip(help, name)">
+ % if help:
+
+ % endif
+%def>
+
+
diff --git a/parameciofast/libraries/templates/utils/edit.phtml b/parameciofast/libraries/templates/utils/edit.phtml
index ba94ffa..6c2ce05 100644
--- a/parameciofast/libraries/templates/utils/edit.phtml
+++ b/parameciofast/libraries/templates/utils/edit.phtml
@@ -1,18 +1,85 @@
<%inherit file="../layout.phtml"/>
<%block name="content">
-
+
+
%block>
<%block name="jscript_block">
+
%block>
diff --git a/parameciofast/modules/fastadmin/__init__.py b/parameciofast/modules/fastadmin/__init__.py
index fe0f601..eaf7494 100644
--- a/parameciofast/modules/fastadmin/__init__.py
+++ b/parameciofast/modules/fastadmin/__init__.py
@@ -1,52 +1,6 @@
-from fastapi import FastAPI, Cookie, Request
-from fastapi.responses import HTMLResponse, RedirectResponse
-from typing import Annotated
-from settings import config
-#from parameciofast.fast import app
-from parameciofast.libraries.session import ParamecioSession
-from starlette.middleware.sessions import SessionMiddleware
-from parameciofast.libraries.db.webmodel import WebModel
-#from parameciofast.fast import app
-
-cookie_name='paramecio_session'
-
-if hasattr(config, 'cookie_name'):
- cookie_name=config.cookie_name
+from fastapi import FastAPI
#print(config.apps['admin'])
-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):
-
- db=WebModel.connection()
-
- request.state.db=db
-
- response = await call_next(request)
-
- db.close()
-
- return response
-
-
-# FastAPI set the middlewares in reversed order.
-
-@admin_app.middleware("http")
-async def check_session(request: Request, call_next):
-
- path=request.scope['path']
-
- if not request.session.get('login_admin', None) and path!=url_app+'login' and path!=url_app+'signup' and path!=url_app+'logout':
-
- return RedirectResponse(request.url_for('login_admin'))
-
- response = await call_next(request)
-
- return response
-
-admin_app.add_middleware(SessionMiddleware, secret_key=config.secret_key, max_age=None, session_cookie=cookie_name, path=url_app)
diff --git a/parameciofast/modules/fastadmin/app.py b/parameciofast/modules/fastadmin/app.py
index ada318e..fa45381 100644
--- a/parameciofast/modules/fastadmin/app.py
+++ b/parameciofast/modules/fastadmin/app.py
@@ -14,12 +14,17 @@ from settings import config
from parameciofast.libraries.datetime import now, format_local_strtime, timestamp_to_datetime, obtain_timestamp
from parameciofast.libraries.keyutils import create_key_encrypt, create_key
from time import time
+from parameciofast.libraries.session import ParamecioSession
+from starlette.middleware.sessions import SessionMiddleware
+from parameciofast.libraries.db.webmodel import WebModel
env=env_theme(__file__)
t=PTemplate(env)
usermodel=UserAdmin()
+url_app=config.apps['admin'][2]
+
login_tries=5
if hasattr(config, 'login_tries'):
@@ -35,6 +40,44 @@ cookie_name='paramecio_session'
if hasattr(config, 'cookie_name'):
cookie_name=config.cookie_name
+cookie_name='paramecio_session'
+
+if hasattr(config, 'cookie_name'):
+ cookie_name=config.cookie_name
+
+# The plugins are added inverse.
+
+@admin_app.middleware('http')
+async def db_connect(request: Request, call_next):
+
+ db=WebModel.connection()
+
+ request.state.db=db
+
+ response = await call_next(request)
+
+ db.close()
+
+ return response
+
+
+# FastAPI set the middlewares in reversed order.
+
+@admin_app.middleware("http")
+async def check_session(request: Request, call_next):
+
+ path=request.scope['path']
+
+ if not request.session.get('login_admin', None) and path!=url_app+'login' and path!=url_app+'signup' and path!=url_app+'logout':
+
+ return RedirectResponse(request.url_for('login_admin'))
+
+ response = await call_next(request)
+
+ return response
+
+admin_app.add_middleware(SessionMiddleware, secret_key=config.secret_key, max_age=None, session_cookie=cookie_name, path=url_app)
+
@admin_app.get('/', response_class=HTMLResponse)
def home_admin(request: Request, paramecio_session: Annotated[str | None, Cookie(description='Cookie for validate into the admin site. The cookie name can change in you settings/config.py')] = None, remote_address: Annotated[str | None, Header()] = None):
@@ -42,6 +85,7 @@ def home_admin(request: Request, paramecio_session: Annotated[str | None, Cookie
return t.load_template('layout.phtml', title=i18n.tlang('Admin'), tlang=i18n.tlang, url_for=app.url_path_for, module_selected='home_admin', session=request.session)
+# Begin the game
@admin_app.get('/login', response_class=HTMLResponse)
def login_admin(request: Request):
diff --git a/parameciofast/modules/fastadmin/ausers.py b/parameciofast/modules/fastadmin/ausers.py
index 9204d3d..30ce4c3 100644
--- a/parameciofast/modules/fastadmin/ausers.py
+++ b/parameciofast/modules/fastadmin/ausers.py
@@ -12,6 +12,7 @@ from pydantic import BaseModel
from parameciofast.libraries.lists import SimpleList
from parameciofast.libraries.formsutils import show_form
from parameciofast.libraries.db.coreforms import SelectForm
+from parameciofast.libraries.db.coreforms import PasswordForm
env=env_theme(__file__)
@@ -74,10 +75,21 @@ def fastadmin_users_edit(item_id: int, request: Request):
forms={k:v for k,v in users.forms.items() if k in fields}
+ #forms['repeat_password']=
+
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)
+ url_edit=request.url_for('fastadmin_users_post', item_id=item_id)
+
+ 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, url_edit=url_edit)
#return ""
+@admin_app.post('/ausers/edit/{item_id}')
+def fastadmin_users_post(item_id: int, request: Request):
+ error=1
+
+ txt_message=''
+
+ return {'error': error, 'txt_message': txt_message}
diff --git a/parameciofast/settings/config.py.sample b/parameciofast/settings/config.py.sample
new file mode 100644
index 0000000..ba18dac
--- /dev/null
+++ b/parameciofast/settings/config.py.sample
@@ -0,0 +1,45 @@
+from parameciofast.libraries.db.webmodel import WebModel
+from parameciofast.libraries.i18n import I18n
+
+from parameciofast.libraries.db.databases.sqlalchemy import SqlClass
+
+SqlClass.disable_pool=True
+
+I18n.dict_i18n=['en-US']
+
+secret_key="hPefRWISrJIY6DBZwCEj2Nyuhp7RTNUI"
+
+static_folder='/media/default'
+
+static_url_path='/media/default'
+
+cookie_name='paramecio_session'
+
+# The second parameter is the prefix if you want use more applications.
+
+#apps={'admin': ['parameciofast.modules.fastadmin', 'admin_app', '/admin/']}
+
+#apps={'admin': ['modules.bot', 'bot_app', '/apibot/']}
+
+default_module='welcome'
+
+theme="default"
+
+reloader=True
+
+application_root='/'
+
+yes_static=True
+
+domain_url='http://localhost:8000'
+
+#media_folder='/media/'
+
+media_url='/media/'
+
+portal_email="antonio.delarosa@salirdelhoyo.com"
+
+
+#Db configuration
+
+WebModel.connections={'default': {'name': 'default', 'host': 'localhost', 'user': 'root', 'password': 'sirena', 'db': 'catalog_db', 'charset': 'utf8mb4', 'set_connection': False, 'db_type': 'pymysql'} }