Merge pull request 'modern' (#2) from modern into master

Reviewed-on: #2
This commit is contained in:
absurdo 2025-03-15 00:00:10 +00:00
commit b4af33d438
163 changed files with 6611 additions and 1387 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "paramecio/modules/admin/media/js/jsutils"]
path = paramecio/modules/admin/media/js/jsutils
url = git@git.cuchulu.com:paramecio/jsutils.git

View file

@ -1,23 +1,21 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@ -72,7 +60,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@ -635,41 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
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 General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
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/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View file

@ -2,8 +2,7 @@ include paramecio/*.py
include README.md
include LICENSE
include REQUIREMENTS
recursive-include paramecio/citoplasma *
recursive-include paramecio/libraries *
recursive-include paramecio/frontend *
recursive-include paramecio/cromosoma *
recursive-include paramecio/settings *
recursive-include paramecio/modules *

View file

@ -12,22 +12,19 @@ Also, you need the next software installed in your os:
### Python 3.4 or later.
Paramecio should work fine in 3.3 but is tested in 3.4 and 3.5 python 3 versions.
Paramecio should work fine since python 3.6 but is tested in 3.12 and 3.13 python 3 versions.
In Debian and Ubuntu you can install Python 3 using the next command: `apt-get install python3`.
In Fedora and other Red Hat derived distros you can use `yum install python3`. In RedHat/Centos 6 or 7 you need install [Ius Repos](https://ius.io/GettingStarted/) for get sane versions of python3. In new Fedora versions you can use dnf, a drop-in replacement for yum: `dnf install python3`.
In Fedora and other Red Hat derived distros you can use `dnf install python3`.
### MySQL or MariaDB database servers.
MariaDB 10.0 and later are recommended.
MariaDB 10.6 and later are recommended.
In Debian and Ubuntu you can install MariaDB using the next command: `apt-get install mariadb-server`.
In Fedora and other Red Hat derived distros you can use `yum install mariadb-server`.
In RedHat/Centos 6 probably you need install adittional repositories for get latest versions of mariadb, but with MySQL 5.5, Paramecio should work fine.
When you will install the mysql server, you should create a new user and database for Paramecio.
In Fedora and other Red Hat derived distros you can use `dnf install mariadb-server`.
### Pip
@ -35,7 +32,7 @@ Pip is the package manager of python. You can use the package manager of your os
In Debian and Ubuntu you can install pip using the next command: `apt-get install python3-pip`.
In Fedora and other Red Hat derived distros you can use `yum install python3-pip`. Of course, the command can change if you use Centos 6/7 with **Ius repos**.
In Fedora and other Red Hat derived distros you can use `dnf install python3-pip`.
### Git
@ -43,7 +40,7 @@ In Fedora and other Red Hat derived distros you can use `yum install python3-pip
In Debian and Ubuntu you can install git using the next command: `apt-get install git`.
In Fedora and other Red Hat derived distros you can use `yum install git` or `dnf install git` in last fedora versions.
In Fedora and other Red Hat derived distros you can use `dnf install git`.
## Install Paramecio Framework
@ -59,8 +56,6 @@ This command will install in your server paramecio framework with its dependenci
When Paramecio finish the installing, you can create your first paramecio site with `paramecio` command.
> If you install passlib and bcrypt python modules, your paramecio install will use bcrypt algorithm for crypt system passwords. If not, default system implementation crypt algorithm (normally the more strong algorithm available in the system) will be used.
### Tipical errors
If you get an error in your installation of dependencies how MarkupSafe or SqlAlchemy, please install gcc or install manually mako and sqlalchemy with your system package manager. For example, for debian and ubuntu:

View file

@ -4,18 +4,18 @@ from bottle import route, get, post, run, default_app, abort, request, response,
from settings import config
#from beaker.middleware import SessionMiddleware
from mimetypes import guess_type
from paramecio.cromosoma.webmodel import WebModel
from paramecio.citoplasma.datetime import set_timezone
from itsdangerous import JSONWebSignatureSerializer
from paramecio.citoplasma.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
from paramecio.libraries.db.webmodel import WebModel
from paramecio.libraries.datetime import set_timezone
#from itsdangerous import JSONWebSignatureSerializer
from paramecio.libraries.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
from paramecio.wsgiapp import app
#from paramecio.citoplasma.sessions import after_session
#from paramecio.libraries.sessions import after_session
modules_pass=False
#app.reset()
#from paramecio.citoplasma.sessions import generate_session
#from paramecio.libraries.sessions import generate_session
#Prepare links for static.
#WARNING: only use this feature in development, not in production.
@ -44,6 +44,10 @@ if hasattr(config, 'error_reporting'):
def prepare_app():
# In mod_wsgi, make strange thing with reloading.
app.reset()
def print_memory():
print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
@ -52,7 +56,7 @@ def prepare_app():
module_loaded=None
#Getting paths for loaded modules for use in media load files
"""
for module in config.modules:
#controller_path=sys.modules[module]
@ -62,6 +66,7 @@ def prepare_app():
base_module=module.split('.')[-1]
arr_module_path[base_module]=controller_base
"""
#app.add_hook('before_request', print_memory)
"""
@ -84,7 +89,7 @@ def prepare_app():
controller_path=import_module(added_app[0])
controller_base=os.path.dirname(controller_path.__file__)
print(controller_base)
dir_controllers=os.listdir(controller_base)
for controller in dir_controllers:
@ -97,23 +102,25 @@ def prepare_app():
a=import_module(module_app)
if added_app[1]!='':
app_name=getattr(a, added_app[1])
#app.register_blueprint(app_name, url_prefix=added_app[2])
#app.mount(added_app[2], app_name)
app_mounts[added_app[2]]=app_name
arr_module_path[key_app]=os.path.dirname(sys.modules[module_app].__file__)
for k_app,v_app in app_mounts.items():
#print(k_app)
if k_app!='/':
app.mount(k_app, v_app)
elif k_app!='':
app.merge(v_app)
set_timezone()
if error_reporting:
from paramecio.citoplasma.error_reporting import ErrorReportingPlugin
from paramecio.libraries.error_reporting import ErrorReportingPlugin
app.install(ErrorReportingPlugin())
@ -144,20 +151,21 @@ application=app
try:
from settings import modules
#from settings import modules
prepare_app()
except:
app.reset()
@app.route('/')
def catch_errors(all='/'):
try:
from pathlib import Path
from settings import modules
#from settings import modules
import time
prepare_app()
p=Path('index.py')
p=Path('app.py')
p.touch()
time.sleep(1)
except:

View file

@ -1,95 +0,0 @@
#!/usr/bin/env python3
from importlib import import_module
from paramecio.citoplasma.sessions import get_session
import json
from bottle import request
yes_session=False
i18n_module={}
def load_lang(*args):
for module in args:
lang_path=module[0]+'.i18n.'+module[1]
try:
i18n_module[lang_path]=import_module(lang_path)
pass
except:
pass
# here load the language
class I18n:
default_lang='en-US'
dict_i18n=['en-US', 'es-ES']
l={}
#@staticmethod
#def set_lang(code_lang):
# if default_lang
@staticmethod
def get_default_lang():
lang=I18n.default_lang
s=get_session()
lang=s.get('lang', lang)
return lang
@staticmethod
def lang(module, symbol, text_default, lang=None):
if not lang:
lang=I18n.get_default_lang()
I18n.l[lang]=I18n.l.get(lang, {})
I18n.l[lang][module]=I18n.l[lang].get(module, {})
I18n.l[lang][module][symbol]=I18n.l[lang][module].get(symbol, text_default)
return I18n.l[lang][module][symbol]
@staticmethod
def extract_value(value):
value=json.loads(value)
lang=I18n.get_default_lang()
if value[lang]!='':
return value[lang]
return value[I18n.default_lang]
@staticmethod
def get_browser_lang():
return request.headers.get('Accept-Language', 'en-US')
@staticmethod
def lang_json(module, symbol, text_default):
arr_final={}
for l in I18n.dict_i18n:
arr_final[l]=I18n.lang(module, symbol, text_default, l)
return json.dumps(arr_final)

View file

@ -1,19 +0,0 @@
from hashlib import sha512, sha256
from base64 import b64encode
from os import urandom
# Functions for create random strings usando urandom
def create_key_encrypt(n=10):
return sha512(urandom(n)).hexdigest()
def create_key_encrypt_256(n=10):
return sha256(urandom(n)).hexdigest()
def create_key(n=10):
rand_bytes=urandom(n)
return b64encode(rand_bytes).decode('utf-8')[0:-2]

View file

@ -7,8 +7,8 @@ import getpass
import re
from pathlib import Path
from base64 import b64encode
from paramecio.cromosoma.webmodel import WebModel
from paramecio.modules.admin.models.admin import UserAdmin
from paramecio.libraries.db.webmodel import WebModel
from paramecio.modules.admin2.models.admin import UserAdmin2, LoginTries2
from subprocess import call
from urllib.parse import urlparse
@ -52,7 +52,7 @@ def start():
print('Error: cannot create the directory. Check if exists and if you have permissions')
exit()
# Create folder settings and copy index.py, admin.py
# Create folder settings and copy app.py, admin.py
path_settings=args.path+'/settings'
@ -74,11 +74,11 @@ def start():
try:
shutil.copy(workdir+'/frontend/index.py', args.path+'/index.py')
shutil.copy(workdir+'/frontend/app.py', args.path+'/app.py')
except:
print('Error: cannot copy the file index.py. Check if exists and if you have permissions for this task')
print('Error: cannot copy the file app.py. Check if exists and if you have permissions for this task')
try:
@ -112,7 +112,7 @@ def start():
print('Error: cannot copy the file create_module.py. Check if exists and if you have permissions for this task')
"""
try:
shutil.copy(workdir+'/settings/modules.py', path_settings+'/modules.py')
@ -120,6 +120,7 @@ def start():
except:
print('Error: cannot copy the file modules.py. Check if exists and if you have permissions for this task')
"""
if args.symlink==True:
try:
@ -221,7 +222,9 @@ def start():
conn=WebModel.connection()
useradmin=UserAdmin(conn)
useradmin=UserAdmin2(conn)
logintries=LoginTries2(conn)
# Check if db exists
@ -248,13 +251,15 @@ def start():
try:
shutil.copy(workdir+'/settings/modules.py.admin', path_settings+'/modules.py')
#shutil.copy(workdir+'/settings/modules.py.admin', path_settings+'/modules.py')
shutil.copy(workdir+'/settings/config_admin.py.sample', path_settings+'/config_admin.py')
#shutil.copy(workdir+'/settings/config_admin.py.sample', path_settings+'/config_admin.py')
sql=useradmin.create_table()
if not useradmin.query(sql):
tries_sql=logintries.create_table()
if not useradmin.query(sql) or not useradmin.query(tries_sql) :
print('Error: cannot create table admin, you can create this table with padmin.py')
else:
@ -265,7 +270,8 @@ def start():
f.close()
config_text=config_text.replace("modules=['paramecio.modules.welcome']", "modules=['paramecio.modules.welcome', 'paramecio.modules.admin', 'paramecio.modules.lang']")
#config_text=config_text.replace("modules=['paramecio.modules.welcome']", "modules=['paramecio.modules.welcome', 'paramecio.modules.lang']")
config_text=config_text.replace("apps={'welcome': ['paramecio.modules.welcome', 'welcome_app', '/'], 'lang': ['paramecio.modules.lang', '', '']}", "apps={'welcome': ['paramecio.modules.welcome', 'welcome_app', '/'], 'lang': ['paramecio.modules.lang', '', ''], 'admin2': ['paramecio.modules.admin2', 'admin_app', '/admin/']}")
with open(path_settings+'/config.py', 'w') as f:
@ -360,7 +366,7 @@ def start():
os.chdir(args.path)
#Regenerating modules.py
"""
regenerate='regenerate.py'
os.chmod(regenerate, 0o755)
@ -370,7 +376,7 @@ def start():
exit(1)
else:
print('Regeneration of modules.py finished')
"""
# Installing models
padmin='padmin.py'
@ -385,7 +391,7 @@ def start():
models_files=os.listdir(models_path)
m=re.compile(".*\.py$")
m=re.compile(r".*\.py$")
underscore=re.compile("^__.*")

View file

@ -8,8 +8,11 @@ import getpass
from pathlib import Path
from settings import config
from importlib import import_module
import re
def start():
"""Module for create new modules for paramecio
"""
parser=argparse.ArgumentParser(description='A tool for create new modules for paramecio')
@ -37,17 +40,68 @@ def start():
#f=open('modules/'+args.path+'/index.py', 'w')
name_module=os.path.basename(args.path)
try:
shutil.copy(workdir+'/examples/index.py', 'modules/'+args.path)
shutil.copy(workdir+'/examples/app.py', 'modules/'+args.path)
with open('modules/'+args.path+'/app.py') as f:
app_file=f.read()
app_file=app_file.replace('/example', '/'+name_module)
with open('modules/'+args.path+'/app.py', 'w') as f:
f.write(app_file)
pass
except:
print('Error: cannot copy controller example. Check if you have permissions')
exit(1)
# Edit config.py
#module_final='modules.'+name_module
module_final=f"'{name_module}': ['modules.{name_module}', '', '']"
try:
with open('./settings/config.py') as f:
#modules_final='\''+'\', \''.join(final_modules)+'\''
p=re.compile(r"^apps=\{(.*)\}$")
#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"apps={\1, "+module_final+"}", line)
final_config+=line
with open('./settings/config.py', 'w') as f:
f.write(final_config)
print('Updated configuration for add new modules...')
except:
print('Cannot update configuration, you need add the new module by hand')
# Reload config
#config.modules.append(module_final)
# Regenerate modules
regenerate_modules_config()
#regenerate_modules_config()
def regenerate_modules_config():
@ -76,7 +130,7 @@ def regenerate_modules_config():
for controller in dir_controllers:
if controller.find('.py')!=-1 and controller.find('__init__')==-1:
if controller.find('.py')!=-1 and controller.find('_')==-1:
controller_py=controller.replace('.py', '')
@ -99,11 +153,8 @@ def regenerate_modules_config():
print("-"*60)
exit(1)
f=open('./settings/modules.py', 'w')
with open('./settings/modules.py', 'w') as f:
f.write("".join(modules))
f.close()
if __name__=="__main__":
start()

View file

@ -1,262 +0,0 @@
from paramecio.cromosoma.webmodel import PhangoField
from paramecio.cromosoma import coreforms
from paramecio.citoplasma.i18n import I18n
class IntegerField(PhangoField):
"""Class that figure an integer sql type field.
Args:
name (str): The name of new field
size (int): The size of the new field in database. By default 11.
required (bool): Boolean for define if
"""
def __init__(self, name, size=11, required=False):
super(IntegerField, self).__init__(name, size, required)
self.default_value=0
def check(self, value):
"""Method for check if value is integer
Args:
value (int): The value to check
"""
self.error=False
self.txt_error=''
try:
value=str(int(value))
if value=="0" and self.required==True:
self.txt_error="The value is zero"
self.error=True
except:
value="0"
self.txt_error="The value is zero"
self.error=True
return value
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'INT('+str(self.size)+') NOT NULL DEFAULT "0"'
class BigIntegerField(IntegerField):
"""Class that figure an big integer sql type field.
Only change the sql type with respect to IntegerField
"""
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'BIGINT('+str(self.size)+') NOT NULL DEFAULT "0"'
class FloatField(PhangoField):
"""Class that figure an float sql type field.
Args:
name (str): The name of new field
size (int): The size of the new field in database. By default 11.
required (bool): Boolean for define if
"""
def __init__(self, name, size=11, required=False):
super(FloatField, self).__init__(name, size, required)
self.error_default="The value is zero"
self.default_value=0
def check(self, value):
"""Method for check if value is integer
Args:
value (float): The value to check
"""
self.error=False
self.txt_error=''
try:
value=str(value)
if value.find(',')!=-1:
value=value.replace(',', '.')
value=str(float(value))
if value==0 and self.required==True:
self.txt_error=self.error_default
self.error=True
except:
value="0"
self.txt_error=self.error_default
self.error=True
return value
def get_type_sql(self):
return 'FLOAT NOT NULL DEFAULT "0"'
class DecimalField(FloatField):
def get_type_sql(self):
return 'DECIMAL(20, 2) NOT NULL DEFAULT "0"'
class DoubleField(FloatField):
def get_type_sql(self):
return 'DOUBLE NOT NULL DEFAULT "0"'
class CharField(PhangoField):
pass
class TextField(PhangoField):
def __init__(self, name, required=False):
super().__init__(name, 11, required)
self.set_default='NOT NULL'
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'TEXT '+self.set_default
class HTMLField(TextField):
def __init__(self, name, required=False):
super().__init__(name, required)
def check(self, value):
return re.sub('<.*?script?>', '', value)
class ForeignKeyField(IntegerField):
def __init__(self, name, related_table, size=11, required=False, identifier_field='id', named_field="id", select_fields=[]):
super(ForeignKeyField, self).__init__(name, size, required)
self.table_id=related_table.name_field_id
self.table_name=related_table.name
self.related_model=related_table
self.identifier_field=identifier_field
self.named_field=named_field
self.select_fields=select_fields
self.foreignkey=True
self.change_form(coreforms.SelectModelForm, [related_table, self.named_field, self.identifier_field])
self.default_value=None
def check(self, value):
value=super().check(value)
if value=='0' or value==0:
value='NULL'
return value
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'INT NULL'
class BooleanField(IntegerField):
def __init__(self, name, size=1):
required=False
self.yes_text=I18n.lang('common', 'yes', 'Yes')
self.no_text=I18n.lang('common', 'no', 'No')
super(IntegerField, self).__init__(name, size, required)
self.default_error="Need 0 or 1 value"
self.default_value=0
def check(self, value):
self.error=False
self.txt_error=''
try:
value=int(value)
if value<0 or value>1:
self.txt_error=self.default_error
self.error=True
except:
self.error=True
self.txt_error=self.default_error
value=0
value=str(value)
return value
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'BOOLEAN NOT NULL DEFAULT "0"'
def show_formatted(self, value):
value=int(value)
if value==0:
value=self.no_text
else:
value=self.yes_text
return str(value)

View file

@ -1,59 +0,0 @@
from paramecio.cromosoma.webmodel import PhangoField,WebModel
import json
class ArrayField(PhangoField):
def __init__(self, name, field_type, required=False):
super().__init__(name, required)
self.field_type=field_type
self.error_default='Sorry, the json array is invalid'
self.set_default='NOT NULL'
def check(self, value):
if type(value).__name__=='str':
try:
value=json.loads(value)
except json.JSONDecodeError:
value=[]
self.error=True
self.txt_error=self.error_default
elif type(value).__name__!='list':
value=[]
self.error=True
self.txt_error='Sorry, the json array is invalid'
if type(self.field_type).__name__!='ArrayField':
for k,v in enumerate(value):
value[k]=self.field_type.check(v)
final_value=json.dumps(value)
final_value=WebModel.escape_sql(final_value)
return final_value
def get_type_sql(self):
return 'TEXT '+self.set_default
def show_formatted(self, value):
return ", ".join(value)
def loads(self, value):
try:
return json.loads(value)
except:
return False

View file

@ -1,47 +0,0 @@
from paramecio.cromosoma.corefields import IntegerField
from paramecio.cromosoma.extraforms.colorform import ColorForm
class ColorField(IntegerField):
def __init__(self, name, size=11, required=False):
super().__init__(name, size, required)
self.name_form=ColorForm
def check(self, value):
value=str(value).replace('#', '0x')
value=int(value, 16)
if value<0 or value>0xffffff:
value=0
return value
def get_hex_color(self, value):
value=str(hex(int(value))).replace('0x', '')
c=len(value)
if(c<6):
repeat=6-c
value=('0'*repeat)+value
value='#'+value
return value
def show_formatted(self, value):
value=str(hex(int(value))).replace('0x', '')
c=len(value)
if(c<6):
repeat=6-c
value=('0'*repeat)+value
value='#'+value
return '<div style="width:50px;height:50px;background-color:%s;"></div>' % value;

View file

@ -1,39 +0,0 @@
from paramecio.cromosoma.corefields import PhangoField
from paramecio.citoplasma import datetime
from paramecio.cromosoma.extraforms.dateform import DateForm
class DateField(PhangoField):
def __init__(self, name, size=255, required=False):
super().__init__(name, size, required)
self.name_form=DateForm
self.utc=True
self.error_default='Error: Date format invalid'
def check(self, value):
if self.utc:
value=datetime.local_to_gmt(value)
elif not datetime.obtain_timestamp(value):
self.error=True
self.txt_error=self.error_default
return ''
if value==False:
self.error=True
self.txt_error=self.error_default
return ''
return value
def show_formatted(self, value):
return datetime.format_date(value)

View file

@ -1,64 +0,0 @@
from paramecio.cromosoma.corefields import PhangoField
from paramecio.citoplasma import datetime
from paramecio.cromosoma.extraforms.dateform import DateForm
class DateTimeField(PhangoField):
def __init__(self, name, size=255, required=False):
super().__init__(name, size, required)
self.name_form=DateForm
self.utc=False
self.error_default='Error: Date format invalid'
def check(self, value):
if self.utc:
value=datetime.local_to_gmt(value)
elif not datetime.obtain_timestamp(value):
self.error=True
self.txt_error=self.error_default
return None
if value==False:
self.error=True
self.txt_error=self.error_default
return None
else:
"""
format_date_txt="YYYY/MM/DD"
format_time_txt="HH:mm:ss"
"""
value=datetime.format_local_strtime('YYYY-MM-DD HH:mm:ss', value)
return value
def show_formatted(self, value):
# Convert to paramecio value
if value!=None:
value=str(value)
value=value.replace('-', '').replace(':', '').replace(' ', '')
return datetime.format_date(value)
else:
return ''
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'DATETIME NULL'

View file

@ -1,54 +0,0 @@
from paramecio.cromosoma.webmodel import WebModel, PhangoField
try:
import ujson as json
except:
import json
class DictField(PhangoField):
def __init__(self, name, field_type, required=False):
super().__init__(name, required)
self.field_type=field_type
self.error_default='Sorry, the json dict is invalid'
self.set_default='NOT NULL'
def check(self, value):
if type(value).__name__=='str':
try:
value=json.loads(value)
except json.JSONDecodeError:
value={}
self.error=True
self.txt_error=self.error_default
elif type(value).__name__!='dict':
value={}
self.error=True
self.txt_error=self.error_default
for k,v in value.items():
value[k]=self.field_type.check(v)
final_value=json.dumps(value)
#final_value=WebModel.escape_sql(final_value)
return final_value
def get_type_sql(self):
return 'TEXT '+self.set_default
def show_formatted(self, value):
return ", ".join(value)

View file

@ -1,27 +0,0 @@
from paramecio.cromosoma.corefields import CharField
import re
mail_pattern=re.compile("\w[\w\.-]*@\w[\w\.-]+\.\w+")
class EmailField(CharField):
def __init__(self, name, size=1024, required=False):
super().__init__(name, size, required)
self.error_default='Error: No valid format'
def check(self, value):
value=super().check(value)
self.error=False
self.txt_error=''
if not mail_pattern.match(value):
self.error=True
value=""
self.txt_error=self.error_default
return value

View file

@ -1,17 +0,0 @@
#!/usr/bin/env python3
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.citoplasma.mtemplates import standard_t
class ColorForm(BaseForm):
def __init__(self, name, value):
super().__init__(name, value)
self.t=standard_t
def form(self):
return self.t.load_template('forms/colorform.phtml', name_form=self.name_field_id, default_value=self.default_value, form=self)

14
paramecio/examples/app.py Normal file
View file

@ -0,0 +1,14 @@
from paramecio.libraries.mtemplates import env_theme, PTemplate
from paramecio.libraries.urls import make_url
from bottle import request
from settings import config
from paramecio.wsgiapp import app
env=env_theme(__file__)
t=PTemplate(env)
@app.route('/example')
def home():
return "Hello World!!"

View file

@ -1,14 +0,0 @@
from paramecio.citoplasma.mtemplates import env_theme, PTemplate
from paramecio.citoplasma.urls import make_url
from bottle import route, request
from settings import config
env=env_theme(__file__)
@route('/example')
def home():
t=PTemplate(env)
return "Hello World!!"

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from paramecio.wsgiapp import app
from paramecio.index import run_app
from paramecio.app import run_app
application=app

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from paramecio.citoplasma.check_i18n import start
from paramecio.libraries.check_i18n import start
start()

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from paramecio.cromosoma.dbadmin import start
from paramecio.libraries.db.dbadmin import start
start()

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.i18n import I18n
I18n.l['en-US']=I18n.l.get('en-US', {})

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.i18n import I18n
I18n.l['en-US']=I18n.l.get('en-US', {})

View file

@ -1,14 +1,14 @@
#!/usr/bin/env python3
from collections import OrderedDict
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.urls import make_url
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.httputils import GetPostFiles
from paramecio.citoplasma.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
from paramecio.cromosoma.formsutils import generate_csrf
from paramecio.libraries.sessions import get_session
from paramecio.libraries.urls import make_url
from paramecio.libraries.i18n import I18n
from paramecio.libraries.httputils import GetPostFiles
from paramecio.libraries.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
from paramecio.libraries.db.formsutils import generate_csrf
from bottle import response,request
from paramecio.cromosoma.webmodel import WebModel
from paramecio.libraries.db.webmodel import WebModel
from time import time
try:
@ -98,7 +98,8 @@ def get_menu(modules_admin):
if len(menu[mod[2]])<4:
menu[mod[2]].append('<i class="fa fa-circle-o" aria-hidden="true"></i>')
else:
menu[mod[2]][3]='<i class="fa {}" aria-hidden="true"></i>'.format(menu[mod[2]][3])
else:
menu[mod[2]]=mod[0]
@ -114,6 +115,8 @@ def get_menu(modules_admin):
if len(menu[submod[2]])<4:
menu[submod[2]].append('<i class="fa fa-circle-o" aria-hidden="true"></i>')
else:
menu[submod[2]][3]='<i class="fa {}" aria-hidden="true"></i>'.format(menu[submod[2]][3])
return menu
@ -158,7 +161,7 @@ def login_model(ModelLogin, session='', enable_tries=False):
user_admin.conditions=['WHERE username=%s', [username]]
arr_user=user_admin.select_a_row_where(['id', 'username', 'password', 'privileges', 'lang', 'num_tries', 'email'])
arr_user=user_admin.select_a_row_where(['id', 'username', 'password', 'privileges', 'lang', 'num_tries', 'email', 'theme'])
if arr_user==False:
@ -180,6 +183,7 @@ def login_model(ModelLogin, session='', enable_tries=False):
s[session+'lang']=arr_user['lang']
s[session+'email']=arr_user['email']
s[session+'username']=arr_user['username']
s[session+'theme']=str(arr_user['theme'])
if s['lang']=='':
s['lang']=I18n.default_lang

View file

@ -1,10 +1,10 @@
#!/usr/bin/env python3
from paramecio.citoplasma.mtemplates import PTemplate
from paramecio.citoplasma.adminutils import check_login, get_language, get_menu
from paramecio.cromosoma.webmodel import WebModel
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.mtemplates import PTemplate
from paramecio.libraries.adminutils import check_login, get_language, get_menu
from paramecio.libraries.db.webmodel import WebModel
from paramecio.libraries.sessions import get_session
from paramecio.libraries.i18n import I18n
try:

View file

@ -5,7 +5,7 @@ import os
import re
from pathlib import Path
from importlib import import_module
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.i18n import I18n
from settings import config
pattern=re.compile('^\w+\.(py|html|phtml|js)$')
@ -48,6 +48,8 @@ def start():
#lang_t=re.compile("\${lang\('("+module_base+"?)',\s+'(.*?)',\s+'(.*?)'\)\}")
lang_t=re.compile("lang\('("+module_base+"?)',\s+'(.*?)',\s+'(.*?)'\)")
lang_s=re.compile("slang\('(.*?)',\s+'(.*?)'\)"
if not os.path.isdir(path):
print("Error: directory to scan doesn't exists")
@ -91,7 +93,7 @@ def start():
file_lang="#!/usr/bin/env python3\n\n"
file_lang+="from paramecio.citoplasma.i18n import I18n\n\n"
file_lang+="from paramecio.libraries.i18n import I18n\n\n"
for lang in I18n.dict_i18n:

View file

@ -6,7 +6,7 @@ try:
from settings import config
except:
config={}
#from paramecio.citoplasma.sessions import get_session
#from paramecio.libraries.sessions import get_session
from os import environ
"""Simple hook for timedate functions from Arrow datetime module. Maybe in the future use native python datetime functions or other libraries. Is simply an abstraction for not depend of particular library.

View file

@ -0,0 +1,426 @@
"""
Parameciofm is a series of wrappers for Bottle.py, 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.webmodel import PhangoField
from paramecio.libraries.db import coreforms
from paramecio.libraries.i18n import I18n
#from bs4 import BeautifulSoup
import bleach
class IntegerField(PhangoField):
"""Class that figure an integer sql type field.
"""
def __init__(self, name, size=11, required=False):
"""
Args:
name (str): The name of field
size (int): The size of the new field in database. By default 11.
required (bool): Boolean for define if field is required or not
"""
super(IntegerField, self).__init__(name, size, required)
self.default_value=0
self.jtype='integer'
self.jformat='int64'
self.jexample='12345'
self.type_sql='int({})'.format(self.size)
def check(self, value):
"""Method for check if value is integer
Args:
value (int): The value to check
"""
self.error=False
self.txt_error=''
try:
value=str(int(value))
if value=="0" and self.required==True:
self.txt_error="The value is zero"
self.error=True
except:
value="0"
self.txt_error="The value is zero"
self.error=True
return value
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'INT('+str(self.size)+') NOT NULL DEFAULT "0"'
class BigIntegerField(IntegerField):
"""Class that figure an big integer sql type field.
Only change the sql type with respect to IntegerField
"""
def __init__(self, name, size=11, required=False):
super().__init__(name, size, required)
self.type_sql='bigint({})'.format(self.size)
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'BIGINT('+str(self.size)+') NOT NULL DEFAULT "0"'
class FloatField(PhangoField):
"""Class that figure an float sql type field.
Args:
name (str): The name of new field
size (int): The size of the new field in database. By default 11.
required (bool): Boolean for define if
"""
def __init__(self, name, size=11, required=False):
super(FloatField, self).__init__(name, size, required)
self.error_default="The value is zero"
self.default_value=0
self.type_sql='float'.format(self.size)
self.jtype='float'
def check(self, value):
"""Method for check if value is integer
Args:
value (float): The value to check
"""
self.error=False
self.txt_error=''
try:
value=str(value)
if value.find(',')!=-1:
value=value.replace(',', '.')
value=str(float(value))
if value==0 and self.required==True:
self.txt_error=self.error_default
self.error=True
except:
value="0"
self.txt_error=self.error_default
self.error=True
return value
def get_type_sql(self):
return 'FLOAT NOT NULL DEFAULT "0"'
class DecimalField(FloatField):
"""PhangoField field for Decimals fields."""
def __init__(self, name, size=11, required=False):
super().__init__(name, size, required)
self.type_sql='decimal(20,2)'
self.jtype='number'
def get_type_sql(self):
return 'DECIMAL(20, 2) NOT NULL DEFAULT "0"'
class DoubleField(FloatField):
"""PhangoField field for Double fields."""
def __init__(self, name, size=11, required=False):
super().__init__(name, size, required)
self.type_sql='double'
self.jtype='float'
def get_type_sql(self):
return 'DOUBLE NOT NULL DEFAULT "0"'
class CharField(PhangoField):
"""Simple alias for PhangoField"""
pass
class TextField(PhangoField):
"""Class used for text fields
Class used for text fields, use TEXT sql type for the this field.
"""
def __init__(self, name, required=False):
"""Init TextField class different to standard PhangoField
Args:
name (str): The name of new field
required (bool): Boolean for define if field is required or not
Attributes:
set_default (str): Set if the value es NOT NULL or not
"""
super().__init__(name, 11, required)
self.type_sql='text'
self.set_default='NOT NULL'
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'TEXT '+self.set_default
class LongTextField(TextField):
"""Class used for long text fields (32 bits size, 4G)
Class used for text fields, use LONGTEXT sql type for the this field.
"""
def __init__(self, name, required=False):
"""Init TextField class different to standard PhangoField
Args:
name (str): The name of new field
required (bool): Boolean for define if field is required or not
Attributes:
set_default (str): Set if the value es NOT NULL or not
"""
super().__init__(name, required)
self.type_sql='longtext'
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'LONGTEXT '+self.set_default
class HTMLField(TextField):
"""Class used for html fields
Class used for html fields, use TEXT sql type for the this field because is children of TextField. In this method self.escape is used for convert " to &quot;
"""
def __init__(self, name, required=False):
"""Init HTMLField class different to standard PhangoField
Args:
name (str): The name of new field
required (bool): Boolean for define if field is required or not
Attributes:
trusted_tags (list): A list with safe tags.
"""
super().__init__(name, required)
self.trusted_tags=[]
def check(self, value):
"""Check method for html values
This check method use beautifulsoap for clean and format html code
"""
# leach.clean('<p>"trial"</p><script></script>', tags=('p'))
"""
soup=BeautifulSoup(value, features='html.parser')
for tag in soup.findAll(True):
if tag.name not in self.trusted_tags:
tag.hidden=True
value=soup.renderContents().decode('utf-8')
if self.escape:
return value.replace('"', '&quot;')
else:
return value
"""
value=bleach.clean('<p>"trial"</p><script></script>', tags=self.trusted_tags)
if self.escape:
return value.replace('"', '&quot;')
else:
return value
class ForeignKeyField(IntegerField):
"""Subclass of IntegerField for create Foreign keys
A subclass of IntegerField used for create foreign keys in related tables.
"""
def __init__(self, name, related_table, size=11, required=False, identifier_field='id', named_field="id", select_fields=[]):
"""
Args:
name (str): Name of field
related_table (WebModel): The table-model related with this foreign key
size (int): The size of the new field in database. By default 11.
required (bool): Boolean for define if field is required or not
identifier_field (str): The Id field name from related table
named_field (str): The field from related table used for identify the row seleted from related table
select_fields (list): A series of fields names from related
"""
super(ForeignKeyField, self).__init__(name, size, required)
self.table_id=related_table.name_field_id
self.table_name=related_table.name
self.related_model=related_table
self.identifier_field=identifier_field
self.named_field=named_field
self.select_fields=select_fields
self.foreignkey=True
self.change_form(coreforms.SelectModelForm, [related_table, self.named_field, self.identifier_field])
self.default_value=None
def check(self, value):
value=super().check(value)
if value=='0' or value==0:
value=None
return value
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'INT NULL'
class BooleanField(IntegerField):
"""Field for boolean values
"""
def __init__(self, name, size=1):
"""
Args:
name (str): Name of field
size (int): The size of the new field in database. By default 11.
"""
required=False
self.yes_text=I18n.lang('common', 'yes', 'Yes')
self.no_text=I18n.lang('common', 'no', 'No')
super(IntegerField, self).__init__(name, size, required)
self.default_error="Need 0 or 1 value"
self.default_value=0
self.type_sql='tinyint(1)'
def check(self, value):
self.error=False
self.txt_error=''
try:
value=int(value)
if value<0 or value>1:
self.txt_error=self.default_error
self.error=True
value=0
except:
self.error=True
self.txt_error=self.default_error
value=0
value=str(value)
return value
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'BOOLEAN NOT NULL DEFAULT "0"'
def show_formatted(self, value):
value=int(value)
if value==0:
value=self.no_text
else:
value=self.yes_text
return str(value)

View file

@ -1,13 +1,55 @@
#!/usr/bin/env python3
"""
Parameciofm is a series of wrappers for Bottle.py, 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 collections import OrderedDict
from html import escape
#Forms para python3
class BaseForm:
"""The class used by all forms classes
BaseForm is the base class used for all form classes.
A form class is used for generate simple html forms, how input type, text type, hidden type, etc. PhangoField classes use this forms for generate automatically forms using GenerateAdminClass and others.
"""
def __init__(self, name, value):
"""
Args:
name (str): The html name for this form
value (str): The default value of this html form.
Attributes:
label (str): Label used in functions how show_form that generate a complete html form from a form class list
name (str): Name of the html form.
default_value (mixed): The default value of the form. Equal to value in typical html form.
css (str): Used for add css classes to the html form
type (str): Variable used for conventional html forms with html tag <input>
field (PhangoField): Field related with this form. Used in PhangoField.
required (boolean): If form is required or not, used in functions that generate forms.
name_field_id (str): The html id for the html form. Used for html things.
help (str): A string with help text, used in functions that generate forms.
"""
self.label=name
self.name=name
@ -17,26 +59,45 @@ class BaseForm:
self.field=None
self.required=False
self.txt_error=''
self.error=False
self.name_field_id=self.name+'_form'
self.help=''
self.placeholder=''
def form(self):
"""Method for returm the html code of the form
"""
return '<input type="'+self.type+'" class="'+self.css+'" name="'+self.name+'" id="'+self.name_field_id+'" value="'+self.setform(self.default_value)+'" />'
return '<input type="'+self.type+'" class="'+self.css+'" name="'+self.name+'" id="'+self.name_field_id+'" value="'+self.setform(self.default_value)+'" placeholder="'+self.placeholder+'" />'
def show_formatted(self, value):
"""Method for show the value of form formatted
Args:
value (mixed): The value of field form
"""
return value
#Method for escape value for html input. DON'T CHANGE IF YOU DON'T KNOWN WHAT ARE YOU DOING
def setform(self, value):
"""A method for set the value in the form for escape and other things
Args:
value (mixed): The value of field form for set
"""
value=str(value)
return value.replace('"', '&quot;').replace("'", '&#39;')
def change_name(self, new_name):
"""A method for change the default form html name of the field form
Args:
new_name (str): The new name of the form. Always is finished with _form suffix
"""
self.name=new_name
@ -45,6 +106,8 @@ class BaseForm:
return ""
class SimpleTextForm(BaseForm):
"""Form for simple text
"""
def __init__(self, name, value):
super().__init__(name, value)
@ -56,6 +119,8 @@ class SimpleTextForm(BaseForm):
return super().form()+' '+self.after_text
class TextForm(BaseForm):
"""Form for simple text form
"""
def __init__(self, name, value):
super(TextForm, self).__init__(name, value)
@ -65,6 +130,8 @@ class TextForm(BaseForm):
return '<textarea class="'+self.css+'" name="'+self.name+'" id="'+self.name+'_form">'+self.setform(self.default_value)+'</textarea>'
class PasswordForm(BaseForm):
"""Form for password forms
"""
def __init__(self, name, value, show_password=False):
super(PasswordForm, self).__init__(name, value)
@ -79,6 +146,8 @@ class PasswordForm(BaseForm):
return value
class HiddenForm(BaseForm):
"""Form for hidden forms
"""
def __init__(self, name, value):
super(HiddenForm, self).__init__(name, value)
@ -86,8 +155,16 @@ class HiddenForm(BaseForm):
class SelectForm(BaseForm):
"""Form for select html form
"""
def __init__(self, name, value, elements=OrderedDict()):
"""
Args:
name (str): The html name for this form
value (str): The default value of this html form
elements (OrderedDict): An ordered dict with the keys(the form value) and text label. Example, if you have a OrderedDict how {'0': 'Value selected'} in a html select form you have the next result: <select name="name"><option value="0">Value selected</option></selected>
"""
super(SelectForm, self).__init__(name, value)
self.arr_select=elements
@ -107,8 +184,19 @@ class SelectForm(BaseForm):
return the_form
class SelectModelForm(SelectForm):
"""Form for select html using a webmodel how base for get the data
"""
def __init__(self, name, value, model, field_name, field_value, field_parent=None):
"""
Args:
name (str): The html name for this form
value (str): The default value of this html form.
model (WebModel): The webmodel used for get the data for arr_select
field_name (str): The field used for get the name of every option in select
field_value (str): The field used for get the value of every option in select
field_parent (int): If the model have parents or children, the value of this argument
"""
super(SelectModelForm, self).__init__(name, value)
try:
@ -129,12 +217,16 @@ class SelectModelForm(SelectForm):
def normal_form(self):
"""Method for prepare the form hierated from SelectForm class, without parents
Method for prepare the form hierated from SelectForm class getting data from database using model attribute.
"""
self.arr_select['']=''
with self.model.select([self.field_name, self.field_value], True) as cur:
for arr_value in cur:
#print(self.model.fields[self.field_name])
self.arr_select[arr_value[self.field_value]]=self.model.fields[self.field_name].show_formatted(arr_value[self.field_name])
try:
@ -147,6 +239,10 @@ class SelectModelForm(SelectForm):
return super().form()
def parent_form(self):
"""Method for prepare the form hierated from SelectForm class, with parents
Method for prepare the form hierated from SelectForm class getting data from database using model attribute.
"""
self.arr_select['']=''
@ -168,7 +264,8 @@ class SelectModelForm(SelectForm):
arr_son[arr_value[self.field_parent]]=[]
arr_son[arr_value[self.field_parent]].append([arr_value[self.field_value], arr_value[self.field_name]])
if arr_value[self.field_value]!=self.model.model_id:
arr_son[arr_value[self.field_parent]].append([arr_value[self.field_value], self.model.fields[self.field_name].show_formatted(arr_value[self.field_name])])
self.create_son(0, arr_son)
@ -186,6 +283,8 @@ class SelectModelForm(SelectForm):
def create_son(self, parent_id, arr_son, separator=''):
"""Recursive method for generate parents and children dictionary
"""
if parent_id in arr_son:
for son in arr_son[parent_id]:

View file

@ -12,6 +12,14 @@ import traceback
engine=None
class SqlClass:
"""Class used how interface to sqlalchemy for connect to mysql engine
Attributes:
cursors_connect (pymysql.cursors.DictCursor): Cursor dict connection to database
disable_pool (boolean): If True then not exists mysql pool, if False, use sql pool for better connections.
pymsql_install (boolean): If True, pymysql is used how mysqldb classic python module.
pool_size (int): The size of the mysql pool.
"""
cursors_connect=None
disable_pool=False
@ -19,8 +27,18 @@ class SqlClass:
pool_size=15
def __init__(self, connection):
"""
Args:
connection (dict): A dict with the configurations of SqlClass connection
Attributes:
error_connection (str): A string where errors are saved
connection (dict): A dict with the configurations of SqlClass connection
conn (MySQL Connection): A PyMySQL or Mysqldb connection
connected (bool): Simple bool for check if was connected to mysql
pool_recycle (int): Time limite for recycle the pool by inactivity
"""
self.max_overflow=-1
#self.max_overflow=-1
self.error_connection=""
# Data of connection
self.connection=connection
@ -28,11 +46,13 @@ class SqlClass:
self.conn=None
self.connected=False
self.pool_recycle=3600
self.connect()
self.last_query=''
self.connect()
def connect(self):
"""Method for connect to mysql db using pymysql or mysqldb
"""
global engine
@ -45,7 +65,8 @@ class SqlClass:
if self.connection['db_type']=='pymysql':
import pymysql.cursors
pymysql.install_as_MySQLdb
SqlClass.pymysql_install=True
SqlClass.cursors_connect=pymysql.cursors.DictCursor
else:
import MySQLdb.cursors
@ -53,6 +74,9 @@ class SqlClass:
engine=create_engine("mysql+%s://%s:%s@%s/%s?charset=utf8mb4" % (self.connection['db_type'], self.connection['user'], self.connection['password'], self.connection['host'], self.connection['db']), pool_recycle=self.pool_recycle, echo_pool=True, pool_size=self.pool_size, pool_pre_ping=True)
#Postgre
#engine = create_engine("postgresql+psycopg2://scott:tiger@localhost:5432/mydatabase")
except:
e = sys.exc_info()[0]
v = sys.exc_info()[1]
@ -77,18 +101,22 @@ class SqlClass:
pymysql.install_as_MySQLdb
SqlClass.pymysql_install=True
self.conn=pymysql.connect(self.connection['host'],
user=self.connection['user'],
passwd=self.connection['password'],
db=self.connection['db'],
"""
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
database='db',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
"""
self.conn=pymysql.connect(host=self.connection['host'], user=self.connection['user'], passwd=self.connection['password'], db=self.connection['db'], charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)
else:
import MySQLdb.cursors
self.conn=MySQLdb.connect(self.connection['host'],
self.conn=MySQLdb.connect(host=self.connection['host'],
user=self.connection['user'],
passwd=self.connection['password'],
db=self.connection['db'],
@ -132,7 +160,13 @@ class SqlClass:
#Make def query more simple if not debugging.
def query(self, sql_query, arguments=[], name_connection="default"):
"""Method for send a sql query to mysql server
Args:
sql_query (str): The sql sentence to execute. For data you should use %s character.
arguments (list): The data used in sql sentence. This data substitute the %s characters.
name_connection (str): The name of dict element with the configuration of connection, without used in this moment.
"""
cursor=self.conn.cursor(SqlClass.cursors_connect)
@ -140,18 +174,20 @@ class SqlClass:
cursor.execute(sql_query, arguments)
self.conn.commit()
if hasattr(cursor, '_last_executed'):
self.last_query=cursor._last_executed
#if hasattr(cursor, '_executed'):
# self.last_query=cursor._executed
return cursor
except:
e = sys.exc_info()[0]
v = sys.exc_info()[1]
if hasattr(cursor, '_last_executed'):
sql_query=cursor._last_executed
#if hasattr(cursor, '_executed'):
# self.last_query=cursor._executed
self.error_connection="Error in query ||%s||Values: %s" % (sql_query, str(arguments))
self.error_connection="Error in query ||%s||Values: %s" % (self.last_query, str(arguments))
self.conn.close()
@ -196,12 +232,16 @@ class SqlClass:
#return cursor.fetchone()
def __del__(self):
"""Typical method used when class is deleted from memory. Close the connextion if exists.
"""
if self.conn:
self.conn.close()
def close(self, name_connection="default"):
"""Method used for close the connection if you want close connection and open other.
"""
if self.conn:

View file

@ -1,5 +1,25 @@
#!/usr/bin/env python3
"""
Parameciofm is a series of wrappers for Bottle.py, 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/>.
"""
import argparse
import os,traceback
import sys, inspect
@ -9,12 +29,19 @@ from datetime import date
from pathlib import Path
from colorama import init, Fore, Back, Style
from importlib import import_module, reload
from paramecio.cromosoma.webmodel import WebModel
from settings import config
from paramecio.libraries.db.webmodel import WebModel
sys.path.insert(0, os.path.realpath('.'))
#from models import books
try:
from settings import config
except:
#print('You need a settings directory with a paramecio2 configuration')
#sys.exit(1)
pass
def start():
"""Function for create and update mysql tables using webmodel classes and fields how source.
"""
connection=WebModel.connection()
@ -103,6 +130,32 @@ def start():
new_tables=[x for x in tables if x not in table_exists]
# Get foreignkeys
# SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA='catalogdev_db' AND information_schema.TABLE_CONSTRAINTS.CONSTRAINT_TYPE = 'FOREIGN KEY' ;
foreignkey_fields={}
#| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA | TABLE_NAME | CONSTRAINT_TYPE |
#+--------------------+-------------------+-----------------------------------------+---------------+-------------------+-----------------+
#| def | catalogdev_db | product_id_attributesIDX | catalogdev_db | attributes | FOREIGN KEY |
#WebModel.connections
db_name=WebModel.connections['default']['db']
with connection.query('SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=%s AND information_schema.TABLE_CONSTRAINTS.CONSTRAINT_TYPE = %s', [db_name, 'FOREIGN KEY']) as cursor:
for row in cursor:
if not row['TABLE_NAME'] in foreignkey_fields:
foreignkey_fields[row['TABLE_NAME']]=[]
foreignkey_fields[row['TABLE_NAME']]=row['CONSTRAINT_NAME'].replace('_{}IDX'.format(row['TABLE_NAME']), '')
pass
#If don't want order
#new_tables=set(tables)-set(table_exists)
@ -125,10 +178,14 @@ def start():
print("--Adding indexes and constraints for the new table "+table)
if table in WebModel.arr_sql_index:
for k_field, index in WebModel.arr_sql_index[table].items():
print("---Added index to "+k_field)
connection.query(index)
if table in WebModel.arr_sql_set_index:
for k_set, index_set in WebModel.arr_sql_set_index[table].items():
if index_set!="":
@ -137,6 +194,8 @@ def start():
print("--Adding uniques elements for the new table")
if table in WebModel.arr_sql_unique:
for k_field, unique_set in WebModel.arr_sql_unique[table].items():
if unique_set!="":
@ -160,6 +219,23 @@ def start():
print(Style.BRIGHT+"Checking old versions of model for find changes...")
for table in tables:
#print(table)
table_fields={table: {}}
# Field | Type | Null | Key | Default | Extra |
#| id | int(11) | NO | PRI | NULL | auto_increment |
with connection.query('describe %s' % table) as cursor:
#all_fields=cursor.fetchall()
#print(all_fields)
for row in cursor:
table_fields[table][row['Field']]={'type': row['Type'], 'key': row['Key']}
pass
#print(table_fields)
#connection.query("")
#Check if new table
@ -182,7 +258,8 @@ def start():
for f, v in WebModel.model[table].fields.items():
if not f in WebModel.model[old_table].fields:
#if not f in WebModel.model[old_table].fields:
if not f in table_fields[table]:
fields_to_add.append(f)
@ -226,13 +303,15 @@ def start():
#Add index
if v.indexed==True and v_old.indexed==False:
#if v.indexed==True and v_old.indexed==False:
if v.indexed==True and table_fields[table][f]['key']!='MUL':
fields_to_add_index.append(f)
changes+=1
if v.indexed==False and v_old.indexed==True:
#if v.indexed==False and v_old.indexed==True:
if v.indexed==False and table_fields[table][f]['key']=='MUL' and v.foreignkey==False:
fields_to_delete_index.append(f)
@ -240,13 +319,15 @@ def start():
#Add unique
if v.unique==True and v_old.unique==False:
#if v.unique==True and v_old.unique==False:
if v.unique==True and table_fields[table][f]['key']!='UNI':
fields_to_add_unique.append(f)
changes+=1
if v.unique==False and v_old.unique==True:
#if v.unique==False and v_old.unique==True:
if v.unique==False and table_fields[table][f]['key']=='UNI':
fields_to_delete_unique.append(f)
@ -254,25 +335,39 @@ def start():
#Add constraint
if v.foreignkey==True and v_old.foreignkey==False:
#if v.foreignkey==True and v_old.foreignkey==False:
if v.foreignkey==True and table_fields[table][f]['key']!='MUL':
fields_to_add_constraint.append(f)
changes+=1
if v.foreignkey==False and v_old.foreignkey==True:
#if v.foreignkey==False and v_old.foreignkey==True:
if v.foreignkey==False and table_fields[table][f]['key']=='MUL':
if table in foreignkey_fields:
if f in foreignkey_fields[table]:
fields_to_delete_constraint.append(f)
changes+=1
for f, v in WebModel.model[old_table].fields.items():
# Clean fields
#for f, v in WebModel.model[old_table].fields.items():
for f, v in table_fields[table].items():
if not f in WebModel.model[table].fields:
#Add constraint
if v.foreignkey==True:
#if v.foreignkey==True:
if table in foreignkey_fields:
if f in foreignkey_fields[table]:
fields_to_delete_constraint.append(f)

View file

@ -0,0 +1,96 @@
"""
Parameciofm is a series of wrappers for Flask, 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.webmodel import PhangoField,WebModel
import json
class ArrayField(PhangoField):
"""Field for save json arrays with determined values"""
def __init__(self, name, field_type, required=False):
"""
Args:
name (str): The name of new field
field_type (PhangoField): The type of PhangoField for save in ArrayField
required (bool): Boolean for define if field is required or not
"""
super().__init__(name, required)
self.field_type=field_type
self.error_default='Sorry, the json array is invalid'
self.set_default=''
self.type_sql='text'
self.jtype='array'
def check(self, value):
if type(value).__name__=='str':
try:
value=json.loads(value)
except json.JSONDecodeError:
value=[]
self.error=True
self.txt_error=self.error_default
elif type(value).__name__!='list':
value=[]
self.error=True
self.txt_error='Sorry, the json array is invalid'
error=0
if type(self.field_type).__name__!='ArrayField':
for k,v in enumerate(value):
value[k]=self.field_type.check(v)
if self.field_type.error:
error+=1
if error>0:
self.error=True
final_value=json.dumps(value)
final_value=WebModel.escape_sql(final_value)
return final_value
def get_type_sql(self):
return 'JSON'
def show_formatted(self, value):
return ", ".join(value)
def loads(self, value):
try:
return json.loads(value)
except:
return False

View file

@ -0,0 +1,72 @@
"""
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.corefields import IntegerField
try:
from paramecio.libraries.db.extraforms.colorform import ColorForm
except:
class ColorForm:
pass
class ColorField(IntegerField):
"""Simple field for save colors in hexadecimal format."""
def __init__(self, name, size=11, required=False):
super().__init__(name, size, required)
self.name_form=ColorForm
self.jtype='string'
def check(self, value):
value=str(value).replace('#', '0x')
value=int(value, 16)
if value<0 or value>0xffffff:
value=0
return value
def get_hex_color(self, value):
value=str(hex(int(value))).replace('0x', '')
c=len(value)
if(c<6):
repeat=6-c
value=('0'*repeat)+value
value='#'+value
return value
def show_formatted(self, value):
value=str(hex(int(value))).replace('0x', '')
c=len(value)
if(c<6):
repeat=6-c
value=('0'*repeat)+value
value='#'+value
return '<div style="width:50px;height:50px;background-color:%s;"></div>' % value;

View file

@ -0,0 +1,67 @@
"""
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.corefields import PhangoField
from paramecio.libraries import datetime
try:
from paramecio.libraries.db.extraforms.dateform import DateForm
except:
class DateForm:
pass
class DateField(PhangoField):
"""Field for use and save dates in YYYYMMDDHHSS format"""
def __init__(self, name, size=255, required=False):
super().__init__(name, size, required)
self.name_form=DateForm
self.utc=True
self.error_default='Error: Date format invalid'
self.jtype='string'
self.jformat='date-time'
def check(self, value):
if self.utc:
value=datetime.local_to_gmt(value)
elif not datetime.obtain_timestamp(value):
self.error=True
self.txt_error=self.error_default
return ''
if value==False:
self.error=True
self.txt_error=self.error_default
return ''
return value
def show_formatted(self, value):
return datetime.format_date(value)

View file

@ -0,0 +1,96 @@
"""
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.corefields import PhangoField
from paramecio.libraries import datetime
try:
from paramecio.libraries.db.extraforms.dateform import DateForm
except:
class DateForm:
pass
class DateTimeField(PhangoField):
"""Field for use and save dates in MySQL date format"""
def __init__(self, name, size=255, required=False):
super().__init__(name, size, required)
self.name_form=DateForm
self.utc=False
self.error_default='Error: Date format invalid in %s' % self.name
self.type_sql='datetime'
self.default_value='0000-00-00 00:00:00'
self.jformat='date-time'
self.jtype='string'
self.jexample='2022-12-01 12:24:11'
def check(self, value):
if self.utc:
value=datetime.local_to_gmt(value)
elif not datetime.obtain_timestamp(value):
self.error=True
self.txt_error=self.error_default+' '+value
return '0000-00-00 00:00:00'
if value==False:
self.error=True
self.txt_error=self.error_default+' '+value
return '0000-00-00 00:00:00'
else:
"""
format_date_txt="YYYY/MM/DD"
format_time_txt="HH:mm:ss"
"""
value=datetime.format_local_strtime('YYYY-MM-DD HH:mm:ss', value)
if value=='':
return '0000-00-00 00:00:00'
return value
def show_formatted(self, value):
# Convert to paramecio value
value=str(value)
value=value.replace('-', '').replace(':', '').replace(' ', '')
return datetime.format_date(value)
def get_type_sql(self):
"""Method for return the sql code for this type
"""
return 'DATETIME NOT NULL'

View file

@ -0,0 +1,85 @@
"""
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.webmodel import WebModel, PhangoField
import json
class DictField(PhangoField):
"""Field for save json dicts with determined values"""
def __init__(self, name, field_type, required=False):
"""
Args:
name (str): The name of field
field_type (PhangoField): The type of PhangoField for save in ArrayField
required (bool): Boolean for define if field is required or not
"""
super().__init__(name, required)
self.field_type=field_type
self.error_default='Sorry, the json dict is invalid'
self.set_default='NOT NULL'
self.type_sql='longtext'
self.jtype='object'
def check(self, value):
if type(value).__name__=='str':
try:
value=json.loads(value)
except json.JSONDecodeError:
value={}
self.error=True
self.txt_error=self.error_default
elif type(value).__name__!='dict':
value={}
self.error=True
self.txt_error=self.error_default
error=0
for k,v in value.items():
value[k]=self.field_type.check(v)
if self.field_type.error:
error+=1
final_value=json.dumps(value)
if error>0:
self.error=True
#final_value=WebModel.escape_sql(final_value)
return final_value
def get_type_sql(self):
return 'JSON '+self.set_default
def show_formatted(self, value):
return ", ".join(value)

View file

@ -0,0 +1,49 @@
"""
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.corefields import CharField
import re
mail_pattern=re.compile(r"\w[\w\.-]*@\w[\w\.-]+\.\w+")
class EmailField(CharField):
"""Field for save and check email addreses"""
def __init__(self, name, size=1024, required=False):
super().__init__(name, size, required)
self.error_default='Error: No valid format in '+self.name
self.jformat='email'
def check(self, value):
value=super().check(value)
self.error=False
self.txt_error=''
if not mail_pattern.match(value):
self.error=True
value=""
self.txt_error=self.error_default+' Email:'+value
return value

View file

@ -1,17 +1,17 @@
import os
import sys
from pathlib import Path
from paramecio.cromosoma.corefields import CharField
from paramecio.cromosoma.extraforms.fileform import FileForm
from paramecio.citoplasma import httputils
from paramecio.citoplasma.keyutils import create_key
from paramecio.libraries.db.corefields import CharField
from paramecio.libraries.db.extraforms.fileform import FileForm
from paramecio.libraries import httputils
from paramecio.libraries.keyutils import create_key
import traceback
from bottle import request
from uuid import uuid4
#from paramecio.cromosoma.extraforms.fileform import FileForm
#from paramecio.libraries.db.extraforms.fileform import FileForm
class FileField(CharField):

View file

@ -1,11 +1,11 @@
#!/usr/bin/env python3
import json
from paramecio.cromosoma.webmodel import PhangoField
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.cromosoma.extraforms.i18nform import I18nForm
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.httputils import GetPostFiles
from paramecio.libraries.db.webmodel import PhangoField
from paramecio.libraries.db.coreforms import BaseForm
from paramecio.libraries.db.extraforms.i18nform import I18nForm
from paramecio.libraries.i18n import I18n
from paramecio.libraries.httputils import GetPostFiles
import json
import re

View file

@ -1,10 +1,10 @@
import os
import sys
from pathlib import Path
from paramecio.cromosoma.corefields import CharField
from paramecio.cromosoma.extraforms.fileform import FileForm
from paramecio.citoplasma import httputils
from paramecio.citoplasma.keyutils import create_key
from paramecio.libraries.db.corefields import CharField
from paramecio.libraries.db.extraforms.fileform import FileForm
from paramecio.libraries import httputils
from paramecio.libraries.keyutils import create_key
import traceback
from bottle import request
@ -16,7 +16,7 @@ except:
from uuid import uuid4
#from paramecio.cromosoma.extraforms.fileform import FileForm
#from paramecio.libraries.db.extraforms.fileform import FileForm
class ImageField(CharField):

View file

@ -1,8 +1,14 @@
from paramecio.cromosoma.corefields import CharField
from paramecio.libraries.db.corefields import CharField
import ipaddress
class IpField(CharField):
def __init__(self, name, size=1024, required=False):
super().__init__(name, size, required)
self.jformat='ipV4'
def check(self, value):
try:

View file

@ -0,0 +1,117 @@
"""
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.webmodel import WebModel, PhangoField
import sys
try:
import ujson as json
except:
import json
class JsonField(PhangoField):
"""Field for save json datatype values"""
def __init__(self, name, field_type, required=False):
"""
Args:
name (str): The name of field
field_type (PhangoField): The type of PhangoField for save in JsonField
required (bool): Boolean for define if field is required or not
"""
super().__init__(name, required)
self.field_type=field_type
self.error_default='Sorry, the json dict is invalid'
self.set_default='NOT NULL'
self.type_sql='longtext'
self.jtype='object'
def check(self, value):
if type(value).__name__=='str':
try:
value=json.loads(value)
except json.JSONDecodeError:
value={}
self.error=True
self.txt_error=self.error_default
elif type(value).__name__!='dict':
value={}
self.error=True
self.txt_error=self.error_default
for k,v in value.items():
value[k]=self.field_type.check(v)
final_value=json.dumps(value)
#final_value=WebModel.escape_sql(final_value)
return final_value
def get_type_sql(self):
return 'JSON '+self.set_default
def show_formatted(self, value):
return ", ".join(value)
# You need check the values previously.
class JsonValueField(PhangoField):
"""Field for save json mixed values. You need check the values previously, the field only check values for prevent sql injections."""
def __init__(self, name, required=False):
super().__init__(name, required)
self.error_default='Sorry, the json dict is invalid'
self.default_value={}
#self.set_default='NOT NULL'
def get_type_sql(self):
return 'JSON'
def check(self, value):
try:
final_value=json.dumps(value)
except json.JSONDecodeError:
final_value='{}'
self.error=True
self.txt_error=self.error_default
return final_value

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from paramecio.cromosoma.corefields import CharField
from paramecio.cromosoma import coreforms
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.db.corefields import CharField
from paramecio.libraries.db import coreforms
from paramecio.libraries.i18n import I18n
class LangField(CharField):

View file

@ -1,4 +1,4 @@
from paramecio.cromosoma.corefields import DecimalField
from paramecio.libraries.db.corefields import DecimalField
from decimal import Decimal, getcontext
from locale import format_string

View file

@ -1,9 +1,9 @@
#!/usr/bin/env python3
#from paramecio.cromosoma.webmodel import PhangoField
from paramecio.cromosoma.corefields import IntegerField
from paramecio.cromosoma.coreforms import SelectModelForm
from paramecio.citoplasma.httputils import GetPostFiles
#from paramecio.libraries.db.webmodel import PhangoField
from paramecio.libraries.db.corefields import IntegerField
from paramecio.libraries.db.coreforms import SelectModelForm
from paramecio.libraries.httputils import GetPostFiles
class ParentField(IntegerField):

View file

@ -1,9 +1,38 @@
from paramecio.cromosoma.corefields import PhangoField
from paramecio.cromosoma.coreforms import PasswordForm
#!/usr/bin/python3
"""
Parameciofm is a series of wrappers for bottle.py, 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.corefields import PhangoField
from paramecio.libraries.db.coreforms import PasswordForm
from hmac import compare_digest as compare_hash
import crypt
from hmac import compare_digest as compare_hash
#try:
# import crypt
#except:
# pass
#import bcrypt
from argon2 import PasswordHasher
class PasswordField(PhangoField):
"""Field for check and save passwords"""
def __init__(self, name, size=1024, required=False):
@ -12,6 +41,7 @@ class PasswordField(PhangoField):
self.name_form=PasswordForm
self.default_value=''
self.encrypt_password=True
self.jformat='password'
def check(self, value):
@ -43,7 +73,10 @@ class PasswordField(PhangoField):
#salt=crypt.mksalt(crypt.METHOD_SHA512)
if self.encrypt_password:
value=crypt.crypt(value)
#value=crypt.crypt(value)
ph=PasswordHasher()
final_value=ph.hash(value)
return final_value
"""
else:
@ -57,8 +90,14 @@ class PasswordField(PhangoField):
@staticmethod
def verify( password, h):
"""Static method used for verify a password save using PasswordField"""
#return bcrypt_sha256.verify(password, h)
return compare_hash(h, crypt.crypt(password, h))
#return compare_hash(h, crypt.crypt(password, h))
ph=PasswordHasher()
try:
return ph.verify(h, password)
except:
return False
# Old function bcrypt

View file

@ -1,4 +1,4 @@
from paramecio.cromosoma.corefields import IntegerField
from paramecio.libraries.db.corefields import IntegerField
class PercentField(IntegerField):

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from paramecio.cromosoma.corefields import CharField
from paramecio.citoplasma.slugify import slugify
from paramecio.cromosoma.coreforms import HiddenForm
from paramecio.libraries.db.corefields import CharField
from paramecio.libraries.slugify import slugify
from paramecio.libraries.db.coreforms import HiddenForm
class SlugifyField(CharField):

View file

@ -1,4 +1,4 @@
from paramecio.cromosoma.corefields import CharField
from paramecio.libraries.db.corefields import CharField
import re
check_url = re.compile(
@ -15,6 +15,7 @@ class UrlField(CharField):
self.error=False
self.txt_error=''
self.jformat='url'
if not check_url.match(value):

View file

@ -1,5 +1,5 @@
from paramecio.cromosoma.corefields import PhangoField
from paramecio.cromosoma.coreforms import PasswordForm
from paramecio.libraries.db.corefields import PhangoField
from paramecio.libraries.db.coreforms import PasswordForm
from hmac import compare_digest as compare_hash
import crypt
import re

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.libraries.db.coreforms import BaseForm
class CheckForm(BaseForm):

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""
Parameciofm is a series of wrappers for bottlepy, 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.coreforms import BaseForm
from paramecio.libraries.mtemplates import standard_t
class ColorForm(BaseForm):
def __init__(self, name, value):
"""Form for get colors from a picker"""
super().__init__(name, value)
self.t=standard_t
def form(self):
return self.t.load_template('forms/colorform.phtml', name_form=self.name_field_id, default_value=self.default_value, form=self)

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.citoplasma.mtemplates import standard_t
from paramecio.citoplasma.datetime import format_timedata
from paramecio.libraries.db.coreforms import BaseForm
from paramecio.libraries.mtemplates import standard_t
from paramecio.libraries.datetime import format_timedata
class DateForm(BaseForm):

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.citoplasma.mtemplates import env_theme, PTemplate
from paramecio.libraries.db.coreforms import BaseForm
from paramecio.libraries.mtemplates import env_theme, PTemplate
env=env_theme(__file__)

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.mtemplates import standard_t
from paramecio.libraries.db.coreforms import BaseForm
from paramecio.libraries.i18n import I18n
from paramecio.libraries.mtemplates import standard_t
import json
class I18nForm(BaseForm):

View file

@ -1,6 +1,6 @@
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.citoplasma.mtemplates import env_theme, PTemplate
from paramecio.libraries.db.coreforms import BaseForm
from paramecio.libraries.mtemplates import env_theme, PTemplate
env=env_theme(__file__)

View file

@ -1,6 +1,6 @@
from paramecio.cromosoma.coreforms import BaseForm
from paramecio.citoplasma.mtemplates import env_theme, PTemplate
from paramecio.libraries.db.coreforms import BaseForm
from paramecio.libraries.mtemplates import env_theme, PTemplate
env=env_theme(__file__)

View file

@ -1,10 +1,10 @@
#!/usr/bin/env python3
from paramecio.cromosoma import corefields
from paramecio.cromosoma.coreforms import PasswordForm
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.keyutils import create_key_encrypt
from paramecio.libraries.db import corefields
from paramecio.libraries.db.coreforms import PasswordForm
from paramecio.libraries.i18n import I18n
from paramecio.libraries.sessionplugin import get_session
from paramecio.libraries.keyutils import create_key_encrypt
from bottle import request
# Need unittest
@ -123,9 +123,10 @@ def csrf_token(token_id='csrf_token'):
s=get_session()
if not 'csrf_token' in s:
#if not 'csrf_token' in s:
s['csrf_token']=create_key_encrypt()
s.save()
#s.save()
return '<input type="hidden" name="csrf_token" class="csrf_token" id="'+token_id+'" value="'+s['csrf_token']+'" />'
@ -135,7 +136,7 @@ def generate_csrf():
if not 'csrf_token' in s:
s['csrf_token']=create_key_encrypt()
s.save()
#s.save()
return s['csrf_token']

View file

@ -0,0 +1,44 @@
# A more simple set for make queries
def insert(model, db, dict_values):
final_values={}
for k in model.fields.keys():
final_values[k]=model.fields[k].check(dict_values.get(k, ''))
del final_values[model.name_field_id]
str_fields="`"+"`, `".join(final_values.keys())+"`"
str_query='insert into {} ({}) VALUES ({})'.format(model.name, str_fields, ", ".join(['%s']*len(final_values)))
success=False
with db.query(str_query, list(final_values.values())) as cursor:
if cursor.rowcount>0:
model.last_id=cursor.lastrowid
success=True
return success
def select(model, db, dict_fields=[], where_sql='', limit='', dict_values=[]):
if len(dict_fields)==0:
dict_fields=['`'+field+'`' for field in model.fields.keys()]
str_fields=", ".join(dict_fields)
str_query='select {} from {} {} limit 1'.format(str_fields, model.name, where_sql)
arr_result=[]
with db.query(str_query, dict_values) as cursor:
arr_result=cursor.fetchall()
return arr_result

View file

@ -1,9 +1,9 @@
#!/usr/bin/env python3
from paramecio.cromosoma.webmodel import WebModel
from paramecio.cromosoma.coreforms import PasswordForm
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.httputils import GetPostFiles
from paramecio.libraries.db.webmodel import WebModel
from paramecio.libraries.db.coreforms import PasswordForm
from paramecio.libraries.i18n import I18n
from paramecio.libraries.httputils import GetPostFiles
class UserModel(WebModel):

View file

@ -1,23 +1,64 @@
#!/usr/bin/env python3
from paramecio.libraries.db.databases.sqlalchemy import SqlClass
from paramecio.libraries.db.coreforms import BaseForm, HiddenForm
import sys
import re
import uuid
from importlib import import_module, reload
from collections import OrderedDict
from paramecio.cromosoma.databases.sqlalchemy import SqlClass
from paramecio.cromosoma.coreforms import BaseForm, HiddenForm
import copy
import traceback
class PhangoField:
"""Base class for fields used in WebModel classes
PhangoField is a class with all elements and variables that you can imagine for a database mysql field in a table. You have many types similar to mysql field types.
"""
def __init__(self, name, size=255, required=False):
"""
Args:
name (str): The name of the field
size (int): The size of sql field.
required (bool): If the field is required or not.
Attributes:
name (str): The name of the field
jtype(Python type): The type of value in python
label (str): A label or generic name for use in text labels used for representate the field
required (bool): If the field is required or not.
size (int): The size of sql field.
protected (bool): If the field can be updated or not in WebModel update method.
quote_close (str): In old versions was used for get more protection for sql sentences
error (bool): If error in query, set to True.
txt_error (str): Variable where the basic text error is saved
model (str): The model where this component or field live
indexed (bool): Property used for set this field how indexed in the database table.
unique (bool): Property used for set this field how unique value in the database table.
foreignkey (bool): Simple property for make more easy identify foreignkeyfields.
default_value (str): Property that define the default value for this field
update (bool): Property that define if this field is in an update operation or insert operation
check_blank (bool): Property used for check if this value cannot change if is in blank and is filled
name_form(BaseForm): Define the form, when is created forms with create_forms you can change the properties of this class
escape (bool): Property that define if make escape in show_formatted. This property control the html transformation of <>" characters in html entities.If false, convert.
file_related (bool): File related: if the field have a file related, delete the file
extra_parameters (list): Extra parameters for the form related with this field
t (PTemplate): Template manager for the form if needed
error_default (str): Error text by default
show_formatted_value (bool): Show this value formatted
help (str): Value used for help strings in tooltips in forms
"""
# The name of the field in database table
self.name=name
# The type of the field in javascript. Util for api documentation
self.jtype='string'
# The label for the Field
self.label=name.replace('_', ' ').title()
@ -50,7 +91,7 @@ class PhangoField:
self.txt_error=""
# Themodel where this component or field live
# The model where this component or field live
self.model=None
@ -100,7 +141,7 @@ class PhangoField:
# Error by default
self.error_default='Error: field required'
self.error_default='Error: '+self.name+' field required'
# Show this value formatted
@ -110,27 +151,28 @@ class PhangoField:
self.help=''
# This method is used for describe the new field in a sql language format.
self.type_sql='varchar({})'.format(self.size)
def get_type_sql(self):
"""This method is used for describe the new field in a sql language format."""
return 'VARCHAR('+str(self.size)+') NOT NULL DEFAULT "'+self.default_value+'"'
def show_formatted(self, value):
"""Method for format the value to show in html or others text outputs"""
return value
# This method for check the value
def check(self, value):
"""Method for check if value is valid for this type field"""
self.error=False
self.txt_error=''
value=str(value).strip()
#Minimal escape for prevent basic js injection.
if self.escape==False:
value=value.replace('<', '&lt;')
@ -150,6 +192,7 @@ class PhangoField:
pass
def create_form(self):
"""Create a BaseForm object for use in forms functions and methods"""
#self.name, self.default_value,
final_parameters=copy.copy(self.extra_parameters)
@ -165,6 +208,7 @@ class PhangoField:
return form
def change_form(self, new_form, parameters):
"""Change the base form of the field and its parameters"""
self.name_form=new_form
@ -174,6 +218,10 @@ class PhangoField:
pass
class PrimaryKeyField(PhangoField):
"""Primary key field based in PhangoField.
This field is used for create a typical id field in a mariadb/mysql table
"""
def __init__(self, name, size=11, required=False):
super(PrimaryKeyField, self).__init__(name, size, required)
@ -181,6 +229,7 @@ class PrimaryKeyField(PhangoField):
self.name_form=HiddenForm
self.required=False
self.error_default="The value is zero"
self.type_sql='int({})'.format(self.size)
def check(self, value):
@ -217,6 +266,24 @@ class PrimaryKeyField(PhangoField):
class WebModel:
"""The most important class for the framework
Webmodel is a class for create objects that represent models. This models are a mirage of SQL tables. You can create fields, add indexes, foreign keys, and more.
Attributes:
arr_sql_index (dict): Internal dict used for generate mysql index in fields
arr_sql_set_index (dict): Internal dict used for generate mysql index in fields
arr_sql_unique (dict): Internal dict used for generate mysql unique values in fields
arr_sql_set_unique (dict): Internal dict used for generate mysql unique values in fields
last_query (str): The last query execute by WebModel
connection_pool (list): A list used in older versions, deprecated
first_primary_key (PrimaryKeyField): Field used for primary field and create models in database.
model (OrderedDict): Dict used for internal things and create tables.
connections (dict): A dict with the configuration of the mysql connection. You can use this element in config.py. You set elements, normally "default" with typical elements how:
host: Database host, user: The username of mysql db, password: The password of user, db: The name of the db, charset: The charset of database, normally utf8, db_type: The db_type, possible values are mysqldb or pymysql, by default, pymysql.
connection_id (str): The id by default of the selected connection from connections.
"""
__slots__=('sqlclass', 'fields', 'forms')
@ -248,12 +315,54 @@ class WebModel:
@staticmethod
def connection():
"""Static method for make a connection using SqlClass
Returns: Return a SqlClass connection for mysql db.
"""
return SqlClass(WebModel.connections['default'])
# Init the class
def __init__(self, sqlclass=None, name_field_id="id"):
"""
Args:
sqlclass (SqlClass): The SqlClass connection used for the mysql db
name_field_id (str): The name of field id of this model/mysql table
Attributes:
name (str): The name of this model correspondient to the sql table name with lower string.
label (str): Descriptive name, first is used self.name how default.
label_general (str): Descriptive general name, first is used self.name how default.
name_field_id (str): The name of field id of this model/mysql table
fields (OrderedDict): A dict with the fields of model/table based in PhangoField objects.
fields_error (OrderedDict): A dict where the errors when check data fields are saved
related (list): A list where related fields are saved.
forms (OrderedDict): A dict where forms related with fields using how base BaseForm class are saved if you use self.create_forms() method.
errors (dict): A dict where generic errors are saved.
num_errors (int): Number of errors generated by the model on query methods.
query_error (str): If error in query, saved here.
values_query (list): Where the values for a sql query for filtering are saved.
conditions (list): A list used for define the sql conditions.
First element is the sql condition, Example: 'WHERE id=%s', and second element is the variable to substitute %s, example [1]. Complete example: ['WHERE id=%s', 1]
order_by (str): Internal variable used for set the sql order str. You don't shoud change this variable if yo don't know what are you doing.
limit (str): Internal variable used for set the sql limit str.
related_models_deleted (list): Internal variable used for delete tables from db.
required_save (str): Internal variable used for required fields defined in self.fields
primary_key (str): Default name of primary key field
yes_reset_conditions (bool): If True, methods how select and update reset self.conditions. If False, self.conditions is used in next select and update executions.
updated (bool): True if the model is used for update, False if the model is used for insert or other operations.
valid_fields (list): List with the fields validated for insert or update
last_id (int): The id of last inserted row.
distinct (str): Add DISTINCT keyword to self.select method.
post (dict): A simple dictionary where post values are saved for use of fields classes
files_delete (dict): A simple dictionary that save the fields that have files related. If i delete the row in database i need delete the files related
sqlclass (SqlClass): A sql_class used for connect to db.
show_formatted (bool): If True, by default all fields are showed with formatted value using show_formatted method of PhangoField classes and children in select method. If False, raw value is showed.
enctype (bool): If True, forms generated using this model are prepared for enctype=multipart/form-data A.K.A. upload files.
model_id (int): Variable where the actual row from model selected can be saved for different things.
field_quote(str): The field delimiter. In Mariadb is `, in PostgreSQL in the future is \"
"""
self.cached=WebModel.global_cached
@ -277,7 +386,7 @@ class WebModel:
# Errors of fields of the table, for safe thread reasons.
self.fields_error=OrderedDict()
#self.fields_error=OrderedDict()
#The tables related with foreignkeyfield to this table
@ -357,12 +466,20 @@ class WebModel:
self.enctype=False
self.model_id=0
self.dummy=0
self.field_quote='`'
# A method for add the connection
def conn(self, sqlclass):
""" Method for get the SqlClass object and prepare sql variables
Args:
sqlclass (SqlClass): A SqlClass object that present the db connection
"""
self.sqlclass=sqlclass
# Reset conditions
@ -371,21 +488,35 @@ class WebModel:
self.conditions=["WHERE 1=1", []]
self.order_by="ORDER BY `"+self.name+"`.`id` ASC"
name_table=self.field_quote+self.name+self.field_quote
field_id=self.field_quote+"id"+self.field_quote
self.order_by="ORDER BY "+name_table+"."+field_id+" ASC"
self.limit=""
# A method for change the name of table
def change_name(self, name):
""" A method for change the name of table
Args;
name (str): The new name of table
"""
self.name=name
self.order_by="ORDER BY `"+self.name+"`.`id` ASC"
name_table=self.field_quote+self.name+self.field_quote
field_id=self.field_quote+"id"+self.field_quote
self.order_by="ORDER BY "+name_table+"."+field_id+" ASC"
# A method where create the new fields of this model
def create_fields(self):
"""Dummy method for use in children classes for add fields"""
#print([i for i in dir(self.__class__) if i[:1] != '_'])
#print(dir(self))
@ -395,6 +526,14 @@ class WebModel:
# A method for register the fields
def register(self, field_model, required=False):
"""A method for register the fields in model class
With this method, your register your fields in the model, inside self.fields attribute. Fields are used for build the query for get the data from the sql table.
Args:
field_model (PhangoField): PhangoField object for add to model
required (bool): If True, the field is required when you insert or update a item row in table model. If False, the field is not required. If field is not required and checking fail, the model update/insert ignore it.
"""
#self.fields_required[field_model]=field_model.required
@ -436,6 +575,13 @@ class WebModel:
# Method for make queries
def query(self, str_query, args=[], connection_id='default'):
"""Method for make typical sql query to db
Args:
str_query (str): The str query. Use the typical format of sql python drivers, example: select * from my_table WHERE id=%s.
args (list): The arguments to substitute %s characters of the strings. The list must sequential with %s characters in the string.
connection_id (str): The connection data used for this connection, by default is "default".
"""
self.connect_to_db()
return self.sqlclass.query(str_query, args, connection_id)
@ -443,6 +589,8 @@ class WebModel:
# Method for clean fields
def clean_fields(self):
"""Method for delete fields from self.fields dict"""
clean=self.fields_to_clean
for field in self.fields_to_clean:
del self.fields[field]
@ -451,6 +599,14 @@ class WebModel:
# External agent define if the update is in code or from external source, how a form.
def insert(self, dict_values, external_agent=True):
"""Insert method, for insert a row in database using a dictionary
This method is a shortcut for typical sql insert sentence.
Args:
dict_values (dict): A dict with the name of the fields how defined in PhangoField for the keys, and values for the values designed for every field.
external_agent (bool): External agent define if the update is in code or from external source, how a form.
"""
self.clean_fields()
@ -466,7 +622,6 @@ class WebModel:
try:
#fields, values, update_values=self.check_all_fields(dict_values, external_agent)
arr_return=self.check_all_fields(dict_values, external_agent)
if arr_return:
@ -476,17 +631,20 @@ class WebModel:
else:
return False
except:
self.query_error='Cannot insert the new row'
print(sys.exc_info()[0])
raise
#return False
except Exception as e:
self.query_error='Cannot insert the new row '+str(e)
#print(sys.exc_info()[0])
return False
c=len(values)
arr_str=['%s' for x in range(c)]
sql="insert into `"+self.name+"` (`"+"`, `".join(fields)+"`) VALUES ("+", ".join(arr_str)+")"
join_fields="`, `".join(fields)
join_values=", ".join(arr_str)
sql="insert into `"+self.name+self.field_quote+" ("+self.field_quote+join_fields+self.field_quote+") VALUES ("+join_values+")"
cursor=self.query(sql, values, self.connection_id)
@ -509,6 +667,15 @@ class WebModel:
# Update method. For update one or many rows.
def update(self, dict_values, external_agent=True):
"""Upate method, for update a row in database using a dictionary
This method is a shortcut for typical sql update sentence.
Args:
dict_values (dict): A dict with the name of the fields how defined in PhangoField for the keys, and values for the values designed for every field.
external_agent (bool): External agent define if the update is in code or from external source, how a form.
"""
self.clean_fields()
@ -545,7 +712,9 @@ class WebModel:
#print(traceback.format_exc())
return False
sql="update `"+self.name+"` SET "+", ".join(update_values)+" "+self.conditions[0]
field_name=self.field_quote+self.name+self.field_quote
sql="update "+field_name+" SET "+", ".join(update_values)+" "+self.conditions[0]
cursor=self.query(sql, values+self.conditions[1], self.connection_id)
@ -583,7 +752,7 @@ class WebModel:
"""
def reset_conditions(self):
"""Method for reset self.conditions to default values"""
self.conditions=["WHERE 1=1", []]
self.limit=''
@ -591,6 +760,19 @@ class WebModel:
#Type assoc can be assoc for return dictionaries
def select(self, arr_select=[], raw_query=False):
"""A method for select fields from a table in db. Support for foreignkeys.
This method is a shortcut for typical sql select sentence. You can select multiple tables using ForeignKeyField class.
Args:
arr_select (dict): A list with the name of the fields how defined in PhangoField.
raw_query (bool): If True, if foreignkeyfields exists, are not selected. If False, foreignkeyfields are selected too if foreignkeyfield are in arr_select.
Returns:
false (bool): If return false, the db connection is down.
sql_cursor (cursor): Return cursor db for get data using loops or other if operation is successful, if not, return False.
"""
self.clean_fields()
@ -608,7 +790,7 @@ class WebModel:
#First table selecction
tables_to_select=['`'+self.name+'`']
tables_to_select=[self.field_quote+self.name+self.field_quote]
keys=list(self.fields.keys())
@ -641,21 +823,21 @@ class WebModel:
else:
arr_repeat_field[self.fields[field].table_name]=0
table_name=self.fields[field].table_name+'` as `'+self.fields[field].table_name+str(arr_repeat_field[self.fields[field].table_name])
table_name=self.fields[field].table_name+self.field_quote+' as '+self.field_quote+self.fields[field].table_name+str(arr_repeat_field[self.fields[field].table_name])
final_table_name=self.fields[field].table_name+str(arr_repeat_field[self.fields[field].table_name])
# The name with its alias of this related table model
tables_to_select.append('`'+table_name+'`')
tables_to_select.append(self.field_quote+table_name+self.field_quote)
# Add field from related table
# as "+table_name+"_"+self.fields[field].named_field
extra_fields.append("`"+final_table_name+"`.`"+self.fields[field].named_field+"` as "+field)
extra_fields.append(self.field_quote+final_table_name+self.field_quote+"."+self.field_quote+self.fields[field].named_field+self.field_quote+" as "+field)
# Add a condition to sql query for join the two tables.
conditions[0]+=" AND `"+final_table_name+"`.`"+self.fields[field].identifier_field+"`=`"+self.name+"`.`"+field+"`"
conditions[0]+=" AND "+self.field_quote+final_table_name+self.field_quote+"."+self.field_quote+self.fields[field].identifier_field+self.field_quote+"="+self.field_quote+self.name+self.field_quote+"."+self.field_quote+field+self.field_quote
# Add extra fields from related table from select_fields ForeignKeyField class member
@ -668,11 +850,11 @@ class WebModel:
# Check if extra_field is ForeignKeyField, if yes, call this function recursively.
extra_fields.append("`"+final_table_name+"`.`"+extra_field+"` as `"+field+"_"+extra_field+"`")
extra_fields.append(self.field_quote+final_table_name+self.field_quote+"."+self.field_quote+extra_field+self.field_quote+" as "+self.field_quote+field+"_"+extra_field+self.field_quote)
else:
# Add normal field to sql query
final_fields.append("`"+self.name+"`.`"+field+"`")
final_fields.append(self.field_quote+self.name+self.field_quote+"."+self.field_quote+field+self.field_quote)
#if len(new_fields)>0:
#self.fields.update(new_fields)
@ -706,16 +888,31 @@ class WebModel:
# Show results in a dictionary
def fetch(self, cursor):
""" Simple method for get a row from db using cursor
Args:
cursor (Db cursor): A typical db cursor of python sql interface standard.
Returns:
row (dict): Return a dictionary with the row selected.
"""
return cursor.fetchone()
def insert_id(self):
"""Method for get the id from last row inserted in table"""
return self.last_id
def element_exists(self, id):
"""Check if exist row with id in db
self.conditions=['WHERE `'+self.name_field_id+'`=%s', [id]]
Args:
id (int): The id of the row to search.
"""
self.conditions=['WHERE '+self.field_quote+self.name_field_id+self.field_quote+'=%s', [id]]
count=self.select_count(self.name_field_id)
@ -731,8 +928,17 @@ class WebModel:
pass
def select_a_row(self, id, fields_selected=[], raw_query=0):
"""Shortcut for get a simple row from a query
self.conditions=['WHERE `'+self.name+'`.`'+self.name_field_id+'`=%s', [id]]
Args:
fields_selected (dict): A list with the name of the fields how defined in PhangoField.
raw_query (bool): If True, if foreignkeyfields exists, are not selected. If False, foreignkeyfields are selected too if foreignkeyfield are in arr_select.
Returns:
row (dict): Returns dict with the row values.
"""
self.conditions=['WHERE '+self.field_quote+self.name+self.field_quote+'.'+self.field_quote+self.name_field_id+self.field_quote+'=%s', [id]]
self.limit="limit 1"
@ -752,6 +958,15 @@ class WebModel:
return row
def select_a_row_where(self, fields_selected=[], raw_query=0, begin=0):
"""Shortcut for get a simple row from a query using self.conditions
Args:
fields_selected (dict): A list with the name of the fields how defined in PhangoField.
raw_query (bool): If True, if foreignkeyfields exists, are not selected. If False, foreignkeyfields are selected too if foreignkeyfield are in arr_select.
Returns:
row (dict): Returns dict with the row values.
"""
self.limit="limit "+str(begin)+", 1"
@ -770,6 +985,16 @@ class WebModel:
def select_to_array(self, fields_selected=[], raw_query=0):
"""Shortcut for get a a list of rows from select sql query
Args:
fields_selected (dict): A list with the name of the fields how defined in PhangoField.
raw_query (bool): If True, if foreignkeyfields exists, are not selected. If False, foreignkeyfields are selected too if foreignkeyfield are in arr_select.
Returns:
row_values (dict): Returns dict with the row values.
"""
if len(fields_selected)==0:
fields_selected=list(self.fields.keys())
@ -810,6 +1035,16 @@ class WebModel:
def select_to_dict(self, fields_selected=[], raw_query=0, integer=True):
"""Shortcut for get a dict of rows from select sql query
Args:
fields_selected (dict): A list with the name of the fields how defined in PhangoField.
raw_query (bool): If True, if foreignkeyfields exists, are not selected. If False, foreignkeyfields are selected too if foreignkeyfield are in arr_select.
Returns:
row (dict): Returns dict with the row values.
"""
if integer:
def check_index(index):
return index
@ -855,6 +1090,15 @@ class WebModel:
# A method por count num rows affected for sql conditions
def select_count(self, field_to_count='id', raw_query=True):
"""Method for get a typical sql count using conditions
Args:
field_to_count (str): The field
raw_query (bool): If True, if foreignkeyfields exists, are not selected. If False, foreignkeyfields are selected too if foreignkeyfield are in arr_select.
Returns:
num_elecments (int): Returns the number of elements selected.
"""
# Connect to db
@ -864,7 +1108,7 @@ class WebModel:
#First table selecction
tables_to_select=['`'+self.name+'`']
tables_to_select=[self.field_quote+self.name+self.field_quote]
fields=list(self.fields.keys())
@ -878,13 +1122,13 @@ class WebModel:
table_name=self.fields[field].table_name
tables_to_select.append('`'+table_name+'`')
tables_to_select.append(self.field_quote+table_name+self.field_quote)
# Add a condition to sql query for join the two tables.
conditions[0]+=" AND `"+table_name+"`.`"+self.fields[field].identifier_field+"`=`"+self.name+"`.`"+field+"`"
conditions[0]+=" AND "+self.field_quote+table_name+self.field_quote+"."+self.field_quote+self.fields[field].identifier_field+self.field_quote+"="+self.field_quote+self.name+self.field_quote+"."+self.field_quote+field+self.field_quote
sql= "select count(`"+field_to_count+"`) from "+", ".join(tables_to_select)+' '+conditions[0]
sql= "select count("+self.field_quote+field_to_count+self.field_quote+") from "+", ".join(tables_to_select)+' '+conditions[0]
count=0
@ -901,12 +1145,19 @@ class WebModel:
# A method for delete rows using sql conditions
def delete(self):
"""Method for delete a series of rows using conditions
Returns:
bool (bool): If delete is successfully, return True, if not, return False.
"""
#self.connect_to_db()
#Need delete rows from other related tables save in self.related_models_deleted
sql=("delete from `"+self.name+"` "+self.conditions[0]+' '+self.order_by+' '+self.limit).strip()
#+' '+self.order_by+' '+self.limit
sql=("delete from "+self.field_quote+self.name+self.field_quote+" "+self.conditions[0]).strip()
result=self.query(sql, self.conditions[1], self.connection_id)
@ -923,6 +1174,15 @@ class WebModel:
return False
def set_conditions(self, sql_text, values:list) -> object:
"""Method for set conditions for a typical sql query
Args:
sql_text (str): The sql text with the conditions, Example: WHERE id=%s
values (list): A list with values for substitute %s characters for the real values filtered for not allow sql injections.
Returns:
Return the same object with self.conditions modified.
"""
self.conditions=[sql_text, values]
@ -930,6 +1190,15 @@ class WebModel:
@staticmethod
def check_in_list(in_list):
"""Method for convert values to int for use in IN (1,2,3) sql sentences.
Args:
in_list (list): List with numbers items.
Returns:
sql_filtered (str): with (1,2,3) sql sentence filtered.
"""
for x in range(0, len(in_list)):
try:
@ -939,6 +1208,15 @@ class WebModel:
return '('+', '.join(in_list)+')'
def check_in_list_str(self, field, in_list):
"""Method for convert values to int for use in IN (value1, value2, value3) sql sentences.
Args:
field (PhangoField): The PhangoField used for check the values of in_list
in_list (list): List with value items.
Returns:
sql_filtered (str): (value1, value2, value3) sql sentence filtered.
"""
for x in range(0, len(in_list)):
in_list[x]=str(self.fields[field].check(in_list[x]))
@ -946,6 +1224,15 @@ class WebModel:
return '("'+'", "'.join(in_list)+'")'
def set_order(self, order:dict) -> object:
""" Method for set and complete the query with "order by" sentences.
Args:
order (dict): A dict with a field name how key, and 0 or 1 how values. 0 define order how ASC, 1 define order how DESC.
Returns:
Returns the same object for execute a query after set_order declaration.
"""
arr_order=[]
arr_order.append('ASC')
@ -964,6 +1251,15 @@ class WebModel:
return self
def set_limit(self, limit: tuple) -> None:
""" Method for set and complete the query with "limit" sentences.
Args:
limit (tuple): A tuple with one or two elements. If one element, example (1), the result is "LIMIT first_element", if two elements, example (1,2), the result is "LIMIT first_element, two_element"
Returns:
Returns the same object for execute a query after set_order declaration.
"""
limit[0]=int(limit[0])
@ -979,6 +1275,7 @@ class WebModel:
# Method for create sql tables
def create_table(self):
"""Method for create a table from this model object"""
#self.connect_to_db()
@ -996,13 +1293,13 @@ class WebModel:
fields=self.fields
for field, data in fields.items():
table_fields.append('`'+field+'` '+data.get_type_sql())
table_fields.append(self.field_quote+field+self.field_quote+' '+data.get_type_sql())
#Check if indexed
if fields[field].indexed==True:
self.arr_sql_index[self.name][field]='CREATE INDEX `index_'+self.name+'_'+field+'` ON '+self.name+'(`'+field+'`);'
self.arr_sql_index[self.name][field]='CREATE INDEX '+self.field_quote+'index_'+self.name+'_'+field+self.field_quote+' ON '+self.name+'('+self.field_quote+field+self.field_quote+');'
self.arr_sql_set_index[self.name][field]=""
@ -1010,20 +1307,20 @@ class WebModel:
if fields[field].unique==True:
self.arr_sql_unique[self.name][field]='ALTER TABLE `'+self.name+'` ADD UNIQUE (`'+field+'`)'
self.arr_sql_unique[self.name][field]='ALTER TABLE '+self.field_quote+self.name+self.field_quote+' ADD UNIQUE ('+self.field_quote+field+self.field_quote+')'
self.arr_sql_set_unique[self.name][field]=""
if type(fields[field]).__name__=="ForeignKeyField":
self.arr_sql_index[self.name][field]='CREATE INDEX `index_'+self.name+'_'+field+'` ON '+self.name+'(`'+field+'`);'
self.arr_sql_index[self.name][field]='CREATE INDEX '+self.field_quote+'index_'+self.name+'_'+field+self.field_quote+' ON '+self.name+'('+self.field_quote+field+self.field_quote+');'
table_related=fields[field].table_name
id_table_related=fields[field].table_id
self.arr_sql_set_index[self.name][field]='ALTER TABLE `'+self.name+'` ADD CONSTRAINT `'+field+'_'+self.name+'IDX` FOREIGN KEY ( `'+field+'` ) REFERENCES `'+table_related+'` (`'+id_table_related+'`) ON DELETE CASCADE ON UPDATE CASCADE;'
self.arr_sql_set_index[self.name][field]='ALTER TABLE '+self.field_quote+self.name+self.field_quote+' ADD CONSTRAINT '+self.field_quote+field+'_'+self.name+'IDX'+self.field_quote+' FOREIGN KEY ( '+self.field_quote+field+self.field_quote+' ) REFERENCES '+self.field_quote+table_related+self.field_quote+' ('+self.field_quote+id_table_related+self.field_quote+') ON DELETE CASCADE ON UPDATE CASCADE;'
return "create table `"+self.name+"` (\n"+",\n".join(table_fields)+"\n) DEFAULT CHARSET=utf8;";
return "create table "+self.field_quote+self.name+self.field_quote+" (\n"+",\n".join(table_fields)+"\n) DEFAULT CHARSET=utf8;";
def update_table(self, fields_to_add, fields_to_modify, fields_to_add_index, fields_to_add_constraint, fields_to_add_unique, fields_to_delete_index, fields_to_delete_unique, fields_to_delete_constraint, fields_to_delete):
@ -1033,40 +1330,40 @@ class WebModel:
print("---Deleting index from "+field+" in "+self.name)
self.query('DROP INDEX `index_'+self.name+'_'+field+'` ON '+self.name, [], self.connection_id)
self.query('DROP INDEX '+self.field_quote+'index_'+self.name+'_'+field+self.field_quote+' ON '+self.name, [], self.connection_id)
for field in fields_to_delete_unique:
print("---Deleting unique from "+field+" in "+self.name)
self.query('DROP INDEX `'+field+'` ON '+self.name, [], self.connection_id)
self.query('DROP INDEX '+self.field_quote+field+self.field_quote+' ON '+self.field_quote+self.name+self.field_quote, [], self.connection_id)
for field in fields_to_delete_constraint:
print("---Deleting foreignkey from "+field+" in "+self.name)
self.query('ALTER TABLE `'+self.name+'` DROP FOREIGN KEY '+field+'_'+self.name+'IDX', [], self.connection_id)
self.query('ALTER TABLE '+self.field_quote+self.name+self.field_quote+' DROP FOREIGN KEY '+field+'_'+self.name+'IDX', [], self.connection_id)
for field in fields_to_delete:
print("---Deleting "+field+" from "+self.name)
self.query('ALTER TABLE `'+self.name+'` DROP `'+field+'`', [], self.connection_id)
self.query('ALTER TABLE '+self.field_quote+self.name+self.field_quote+' DROP '+self.field_quote+field+self.field_quote, [], self.connection_id)
#Deleting indexes and constraints.
#Obtain new fields
for field in fields_to_modify:
print("---Updating "+field+" in "+self.name)
self.query('ALTER TABLE `'+self.name+'` MODIFY `'+field+'` '+self.fields[field].get_type_sql(), [], self.connection_id)
self.query('ALTER TABLE '+self.field_quote+self.name+self.field_quote+' MODIFY '+self.field_quote+field+self.field_quote+' '+self.fields[field].get_type_sql(), [], self.connection_id)
for field in fields_to_add:
print("---Adding "+field+" in "+self.name)
self.query('ALTER TABLE `'+self.name+'` ADD `'+field+'` '+self.fields[field].get_type_sql(), [], self.connection_id)
self.query('ALTER TABLE '+self.field_quote+self.name+self.field_quote+' ADD '+self.field_quote+field+self.field_quote+' '+self.fields[field].get_type_sql(), [], self.connection_id)
for field in fields_to_add_index:
print("---Adding index to "+field+" in "+self.name)
self.query('CREATE INDEX `index_'+self.name+'_'+field+'` ON '+self.name+' (`'+field+'`);', [], self.connection_id)
self.query('CREATE INDEX '+self.field_quote+'index_'+self.name+'_'+field+self.field_quote+' ON '+self.name+' ('+self.field_quote+field+self.field_quote+');', [], self.connection_id)
for field in fields_to_add_constraint:
@ -1076,20 +1373,26 @@ class WebModel:
id_table_related=self.fields[field].table_id
self.query('ALTER TABLE `'+self.name+'` ADD CONSTRAINT `'+field+'_'+self.name+'IDX` FOREIGN KEY ( `'+field+'` ) REFERENCES `'+table_related+'` (`'+id_table_related+'`) ON DELETE CASCADE ON UPDATE CASCADE;', [], self.connection_id)
self.query('ALTER TABLE '+self.field_quote+self.name+self.field_quote+' ADD CONSTRAINT '+self.field_quote+field+'_'+self.name+'IDX'+self.field_quote+' FOREIGN KEY ( '+self.field_quote+field+self.field_quote+' ) REFERENCES '+self.field_quote+table_related+self.field_quote+' ('+self.field_quote+id_table_related+self.field_quote+') ON DELETE CASCADE ON UPDATE CASCADE;', [], self.connection_id)
for field in fields_to_add_unique:
print("---Adding unique to "+field+" in "+self.name)
self.query('ALTER TABLE `'+self.name+'` ADD UNIQUE (`'+field+'`)', [], self.connection_id)
self.query('ALTER TABLE '+self.field_quote+self.name+self.field_quote+' ADD UNIQUE ('+self.field_quote+field+self.field_quote+')', [], self.connection_id)
# Method for drop sql tables and related
def drop(self):
return self.query('DROP TABLE '+self.name, [], self.connection_id)
"""Method for drop a table based in this model
Returns:
sql_str (str): Return the sql query for drop the table represented by this model
"""
return self.query('DROP TABLE '+self.field_quote+self.name+self.field_quote, [], self.connection_id)
#Return an array with all fields
@ -1099,6 +1402,20 @@ class WebModel:
#Check of all fields in table.
def check_all_fields(self, dict_values, external_agent, yes_update=False, errors_set="insert"):
"""Method for check all fields of a model for insert or update a row in table db.
Args:
dict_values (dict): The dict of values to check
external_agent (bool): If True, the query is considered manipulated by external agent and the checks are stricts, if not, checks are not stricts
yes_update (bool): If True, the check need be done for update sql sentence, if False, the check is done for insert sql sentence
errors_set (str): If insert value, the errors are set for insert sql statement, if update value, then the errors are set for update sql statement.
Returns:
wrong (bool): Return False if checking is wrong. If not False returns a tuple with fields filtered, original values as values and values filtered how update_values
fields (list): list with fields
values (dict): dict with values
update_values (dict): dict with updated values with checking
"""
fields=[]
values=[]
@ -1115,7 +1432,7 @@ class WebModel:
error=False
if yes_update==True:
f_update=lambda field, value: "`"+field+"`=%s"
f_update=lambda field, value: self.field_quote+field+self.field_quote+"=%s"
else:
f_update=lambda field, value: ""
@ -1227,6 +1544,7 @@ class WebModel:
#Reset the require field in fields
def reset_require(self):
"""Reset the require attribute in fields"""
for k, v in self.fields.items():
@ -1237,6 +1555,7 @@ class WebModel:
#Reload the require field in fields
def reload_require(self):
"""Reload the require field in fields"""
for k,r in self.fields.items():
self.fields[k].required=r
@ -1244,6 +1563,7 @@ class WebModel:
#Choose all fields to updated
def set_valid_fields(self, fields={}):
"""Choose all fields to updated"""
if len(fields)==0:
fields=self.fields.keys()
@ -1253,6 +1573,7 @@ class WebModel:
#Create a form based in table.
def create_forms(self, arr_fields=[]):
"""Create a form based in table."""
self.forms=OrderedDict()
@ -1270,6 +1591,12 @@ class WebModel:
return arr_fields
def create_form_after(self, form_after, new_form):
"""Create form after other form
Args:
form_after (str): The name of the form where the new form is located next
new_form (BaseForm): The BaseForm or derivated class used for create the new form.
"""
new_dict=OrderedDict()
@ -1281,6 +1608,11 @@ class WebModel:
self.forms=new_dict
def show_errors(self):
"""Get all errors of model last operation.
Returns:
error_txt (str): A string with all errors.
"""
arr_error=[]
error_txt=''
@ -1301,6 +1633,11 @@ class WebModel:
return error_txt
def collect_errors(self):
"""Get all errors and save in dictionary
Returns:
errors (dict): Return a dict where the key is the field where the error exists and value is the error text.
"""
arr_error= {}
error_txt=''
@ -1318,12 +1655,15 @@ class WebModel:
return arr_error
def safe_query(self):
"""Method for reset require for fields.
With this method you can make queries without real checks, except mysql injection safe variables."""
self.create_forms()
self.reset_require()
def close(self):
"""Method for close sqlclass db connection"""
self.sqlclass.close()
@ -1341,6 +1681,7 @@ class WebModel:
#del sqlclass.connection[key]
@staticmethod
def escape_sql(value):
"""Manual escape for sql, you shouldn't use it"""
value=str(value)
@ -1354,6 +1695,11 @@ class WebModel:
# Set post values from a post array
def set_post_values(self, post):
"""Prepare a dict with values using fields keys how base
Returns:
post (dict): Return a dict with values without checking anything.
"""
for k in self.fields.keys():
@ -1373,6 +1719,6 @@ class QueryModel(WebModel):
self.label_general=self.name
self.order_by="ORDER BY `"+self.name+"`.`id` ASC"
self.order_by="ORDER BY "+self.field_quote+self.name+self.field_quote+"."+self.field_quote+"id"+self.field_quote+" ASC"

View file

@ -3,7 +3,7 @@
# A bottle plugin for send emails if
from settings import config
from paramecio.citoplasma.sendmail import SendMail
from paramecio.libraries.sendmail import SendMail
import sys, traceback
email_failed=''

View file

@ -1,10 +1,10 @@
from paramecio.citoplasma.lists import SimpleList
from paramecio.libraries.lists import SimpleList
from bottle import request
from paramecio.citoplasma.urls import add_get_parameters, redirect
from paramecio.citoplasma.mtemplates import set_flash_message
from paramecio.cromosoma.formsutils import show_form
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.httputils import GetPostFiles
from paramecio.libraries.urls import add_get_parameters, redirect
from paramecio.libraries.mtemplates import set_flash_message
from paramecio.libraries.db.formsutils import show_form
from paramecio.libraries.i18n import I18n
from paramecio.libraries.httputils import GetPostFiles
from collections import OrderedDict
class GenerateAdminClass:
@ -49,6 +49,8 @@ class GenerateAdminClass:
self.url_redirect=self.url
self.pre_update=None
self.post_update=None
self.text_home=I18n.lang('common', 'home', 'Home')
@ -126,6 +128,9 @@ class GenerateAdminClass:
title_edit=I18n.lang('common', 'edit_new_item', 'Edit item')
self.model.conditions=['WHERE `'+self.model.name+'`.`'+self.model.name_field_id+'`=%s', [getpostfiles.get['id']]]
if self.pre_update:
getpostfiles.post=self.pre_update(self, getpostfiles.post)
if insert_row(getpostfiles.post):
set_flash_message(I18n.lang('common', 'task_successful', 'Task successful'))
@ -209,6 +214,8 @@ class GenerateConfigClass:
self.template_insert='utils/insertform.phtml'
self.pre_update=None
self.post_update=None
self.text_home=I18n.lang('common', 'home', 'Home')

View file

@ -1,6 +1,6 @@
#/usr/bin/env python3
from paramecio.citoplasma.urls import add_get_parameters
from paramecio.libraries.urls import add_get_parameters
class HierarchyLinks:

View file

@ -2,12 +2,12 @@
import json, re
from bottle import request, response
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.keyutils import create_key_encrypt
from paramecio.libraries.sessionplugin import get_session
from paramecio.libraries.keyutils import create_key_encrypt
from bottle import HTTPResponse
no_csrf=False
change_csrf=False
change_csrf=True
try:
@ -84,6 +84,8 @@ class GetPostFiles:
self.post={}
if not request.json:
try:
self.post=request.forms.decode('utf-8')
@ -93,6 +95,11 @@ class GetPostFiles:
request.forms.recode_unicode=False
self.post=request.forms.decode('utf-8')
else:
self.post=request.json
#print(self.post.keys())
if len(required_post)==0:
required_post=self.post.keys()
@ -100,7 +107,7 @@ class GetPostFiles:
self.post[post]=self.post.get(post, '')
s=get_session()
#print('s', s)
if ignore_csrf_token==False and no_csrf==False:
if 'csrf_token' in s:
@ -111,9 +118,9 @@ class GetPostFiles:
# Clean csrf_token
del s['csrf_token']
#del s['csrf_token']
s.save()
#s.save()
#raise NameError('Error: you need a valid csrf_token')
raise HTTPResponse(body=json.dumps({'error_csrf': 1, 'error': 1, 'token_invalid': 1}), status=200, headers={'Content-type': 'application/json'})
@ -124,7 +131,7 @@ class GetPostFiles:
del s['csrf_token']
s.save()
#s.save()
else:
@ -160,8 +167,8 @@ def check_csrf(post):
del s['csrf_token']
s.save()
#s.save()
else:
#raise NameError('Error: you don\'t send any valid csrf_token')
raise HTTPResponse(body=json.dumps({'error_csrf': 1, 'error': 1, 'token_invalid': 0}), status=200, headers={'Content-type': 'application/json'})
raise HTTPResponse(body=json.dumps({'error_csrf': 1, 'error': 1, 'message': 'Error: csrf token invalid', 'token_invalid': 0}), status=200, headers={'Content-type': 'application/json'})

212
paramecio/libraries/i18n.py Normal file
View file

@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
Parameciofm is a series of wrappers for bottle.py, 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 importlib import import_module
from paramecio.libraries.sessionplugin import get_session
import json
from bottle import request
import gettext
import os
yes_session=False
i18n_module={}
def load_lang(*args):
"""A function for load the lang module dinamically
"""
for module in args:
lang_path=module[0]+'.i18n.'+module[1]
try:
i18n_module[lang_path]=import_module(lang_path)
pass
except:
pass
# here load the language
class PGetText:
# Dict where all gettext domain are saved -> domain=name, example, admin, libraries, pastafari2, etc...
l={}
def __init__(self, module_file):
module_dir=os.path.dirname(os.path.realpath(module_file))
module_name=os.path.basename(module_dir)
if module_name not in PGetText.l:
PGetText.l[module_name]={}
for i in I18n.dict_i18n:
if i not in PGetText.l[module_name]:
PGetText.l[module_name][i]=gettext.translation(module_name, module_dir+'/languages/', languages=[i], fallback=True)
PGetText.l[module_name][i].install()
self.module=module_name
def gettext(self, text):
return PGetText.l[self.module][I18n.get_default_lang()].gettext(text)
def pgettext(module_file):
module=os.path.dirname(os.path.realpath(module_file))
base_name=os.path.dirname(os.path.realpath(module))
l=gettext.translation(os.path.basename(base_name), module+'/languages/', languages=I18n.get_default_lang(), fallback=True)
return l.gettext
class I18n:
"""Class for i18n tasks
Class for i18n tasks, how, strings for every language supported, for now are en-US and es-ES. You can add more languages adding
Attributes:
default_lang (str): The default string lang used when get someone
dict_i18n (list): The list with default languages. You can add more calling it static variable in settings/config.py
"""
default_lang='en-US'
dict_i18n=['en-US', 'es-ES']
l={}
def __init__(self, module):
self.module=module
def slang(self, symbol, text_default, lang=None):
"""Method for get a string from selected language but object oriented
Method for get a string from selected language but object oriented
Args:
symbol (str): The symbol used for identify the text string.
text_default (str): The text default used. You have use how base for translations.
"""
return I18n.lang(self.module, symbol, text_default, lang)
def tlang(self, text_default, lang=None):
"""Method for get a string from selected language but object oriented and using module and symbol by default
Method for get a string from selected language but object oriented and using module and symbol by default
Args:
symbol (str): The symbol used for identify the text string.
text_default (str): The text default used. You have use how base for translations.
"""
symbol=text_default[:60]
return I18n.lang(self.module, symbol, text_default, lang)
#@staticmethod
#def set_lang(code_lang):
# if default_lang
@staticmethod
def get_default_lang():
"""Static method for get the default lang"""
lang=I18n.default_lang
s=get_session()
lang=s.get('lang', lang)
return lang
@staticmethod
def lang(module, symbol, text_default, lang=None):
"""Static method for get a string from selected language
Static method used to get the string of the selected language. If there is no string in the selected language, it returns text_default.
Args:
module (str): The module to which the translation string belongs
symbol (str): Simple symbol that is useful for identify the string
text_default (str): The text used by default when there are not translation in the selected language
"""
if not lang:
lang=I18n.get_default_lang()
I18n.l[lang]=I18n.l.get(lang, {})
I18n.l[lang][module]=I18n.l[lang].get(module, {})
I18n.l[lang][module][symbol]=I18n.l[lang][module].get(symbol, text_default)
return I18n.l[lang][module][symbol]
@staticmethod
def extract_value(value):
"""Static method for get values from json lang array
Args:
value (json): Lang dict in json format
"""
value=json.loads(value)
lang=I18n.get_default_lang()
if value[lang]!='':
return value[lang]
return value[I18n.default_lang]
@staticmethod
def get_browser_lang():
return request.headers.get('Accept-Language', 'en-US')
@staticmethod
def lang_json(module, symbol, text_default):
arr_final={}
for l in I18n.dict_i18n:
arr_final[l]=I18n.lang(module, symbol, text_default, l)
return json.dumps(arr_final)
common_pgettext=PGetText(__file__)

View file

@ -1,5 +1,5 @@
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.sessions import get_session
from paramecio.libraries.i18n import I18n
from settings import config
def make_js_url(file_path, module):

View file

@ -0,0 +1,86 @@
"""
Parameciofm is a series of wrappers for bottlepy, 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 hashlib import sha512, sha256
from base64 import b64encode
from os import urandom
import string
import secrets
# Functions for create random strings usando urandom
def create_key_encrypt(n=10):
""" Simple function for create a random string
Simple function for create a random string based in sha512
Args:
n (int): size of string random bytes (view urandom function in Python3 Help)
"""
return sha512(urandom(n)).hexdigest()
def create_key_encrypt_256(n=10):
""" Simple function for create a random string
Simple function for create a random string based in sha256
Args:
n (int): size of string random bytes (view urandom function in Python3 Help)
"""
return sha256(urandom(n)).hexdigest()
def create_key(n=10):
""" Simple function for create a random string
Simple function for create a random string based in urandom function and base64 encoding
Args:
n (int): size of string random bytes (view urandom function in Python3 Help)
"""
rand_bytes=urandom(n)
return b64encode(rand_bytes).decode('utf-8')[0:-2]
def create_simple_password(n=14):
""" Based in python3 documentation for create passwords using secrets module
https://docs.python.org/3/library/secrets.html
Args:
n (int): Number of random elements of the password
"""
password=''
alphabet=string.ascii_letters+string.digits+string.punctuation
while True:
password=''.join(secrets.choice(alphabet) for i in range(n))
if (any(c.islower() for c in password) and any(c.isupper() for c in password) and sum(c.isdigit() for c in password) >= 3):
break
return password

View file

@ -1,14 +1,14 @@
#By default id is not showed
from paramecio.citoplasma.pages import Pages
from paramecio.citoplasma.urls import add_get_parameters
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.httputils import GetPostFiles
from paramecio.libraries.pages import Pages
from paramecio.libraries.urls import add_get_parameters
from paramecio.libraries.sessions import get_session
from paramecio.libraries.i18n import I18n
from paramecio.libraries.httputils import GetPostFiles
from bottle import request
import sys
import re
from paramecio.citoplasma.pages import Pages
from paramecio.libraries.pages import Pages
class SimpleList:

View file

@ -1,22 +1,58 @@
#!/usr/bin/python
"""
Parameciofm is a series of wrappers for Bottle.py, 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 bottle import hook
from mako.template import Template
from mako.lookup import TemplateLookup
from paramecio.citoplasma.urls import make_url, make_url_domain, make_media_url, make_media_url_module, add_get_parameters
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.adminutils import make_admin_url
from paramecio.cromosoma.formsutils import csrf_token
from paramecio.citoplasma.js import make_js_url
from paramecio.libraries.urls import make_url, make_url_domain, make_media_url, make_media_url_module, add_get_parameters
from paramecio.libraries.i18n import I18n, PGetText
from paramecio.libraries.sessionplugin import get_session
from paramecio.libraries.adminutils import make_admin_url
from paramecio.libraries.db.formsutils import csrf_token
from paramecio.libraries.js import make_js_url
from settings import config
from os import path
from collections import OrderedDict
from paramecio.wsgiapp import app
# Preparing envs for views of modules, and views of
def env_theme(module, cache_enabled=True, cache_impl='', cache_args={}, module_directory="./tmp/modules"):
"""Function for create an environment for mako templates
Function for create an environment for mako templates. Really is a shortcut for TemplateLookup mako function. You can define cache options and module_directory for templates compiled
Args:
module (str): The module where the templates can be founded
cache_enabled (boolean): If True then mako template cache is enabled, is False, mako template cache is disabled.
cache_args (dict): Cache Args dict parameter for TemplateLookup function from Mako templates. View Mako Templates documentation.
module_directory (str): Module directory parameter for TemplateLookup function from Mako templates. View Mako Templates documentation.
Returns:
template (TemplateLookup): Return TemplateLookup object
"""
ext=module[len(module)-3:]
if ext=='.py':
@ -50,12 +86,33 @@ def preload_templates(template_files, env):
return templates
def url_for(name, **kwargs):
return app.get_url(name, **kwargs)
class PTemplate:
"""A class used how shortcuts for Mako template functions.
"""
templates_loaded={}
def __init__(self, environment):
"""A class used how shortcuts for Mako template functions.
This class is used to have a set of shortcuts and hooks to Mako templates functions and methods over a series of default options.
Args:
environment (TemplateLookup): A TemplateLookup object generated with env_theme function
Attributes:
autoescape_ext (set): A set of extensions file where automatic autoescape is used
environment (TemplateLookup): A TemplateLookup object generated with env_theme function
filters (list): A list of functions used for add filters to your templates.
js (list): A list of javascript sources for generate js html load tags.
"""
# A simple method used in internal things of paramecio
self.show_basic_template=True
@ -130,6 +187,32 @@ class PTemplate:
self.env=environment
# Loading language domain for gettext in templates
base_name=path.dirname(path.realpath(__file__))
module_env=self.env.directories[1].replace('/templates', '')
self.l=PGetText(module_env+'/app.py')
self.add_filter(self._)
self.i18n=I18n(base_name)
self.add_filter(self.i18n.slang)
self.add_filter(self.i18n.tlang)
#self.url_for=lambda name: app.get_url(name)
#x = lambda a : a + 10
#print(self.url_for)
self.add_filter(url_for)
def _(self, text):
return self.l.gettext(text)
#self.auto_reload=True
# Clean HeaderHTML
@ -144,6 +227,12 @@ class PTemplate:
def guess_autoescape(self, template_name):
"""Simple helper method for get an extension from filename
Args:
template_name (str): The template name
"""
if template_name is None or '.' not in template_name:
return False
@ -180,6 +269,18 @@ class PTemplate:
def load_template(self, template_file, **arguments):
"""Load a mako template and return the result
Load a mako template and return the results with different arguments applied
Args:
template_file (str): The name of template file. The template is searched using configuration defined in self.env
**arguments (mixed): Extra arguments with variables passed to template
Returns:
template (str): Return a template rendered using mako class from self.env
"""
template = self.env.get_template(template_file)
arguments.update(self.filters)
@ -190,6 +291,18 @@ class PTemplate:
def render_template(self, template_file, **arguments):
"""Experimental method for parse a template
Experimental method for parse a template, similar to load_template but try cache the template loaded
Args:
template_file (str): The name of template file. The template is searched using configuration defined in self.env
**arguments (mixed): Extra arguments with variables passed to template
Returns:
dummy (str): Dummy return necessary because mako expect return something
"""
if not str(self.env.directories)+'/'+template_file in PTemplate.templates_loaded:
PTemplate.templates_loaded[str(self.env.directories)+'/'+template_file]=self.env.get_template(template_file)
@ -335,7 +448,7 @@ class HeaderHTML:
s['flash']=''
s.save()
#s.save()
return message
@ -345,7 +458,7 @@ def set_flash_message(message):
s['flash']=message
s.save()
#s.save()
def qf(text):

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
from math import ceil, floor
from paramecio.citoplasma.urls import add_get_parameters
from paramecio.citoplasma.i18n import I18n
from paramecio.libraries.urls import add_get_parameters
from paramecio.libraries.i18n import I18n
class Pages:

View file

@ -1,8 +1,8 @@
from paramecio.modules.admin.models.admin import UserAdmin
from bottle import request
from paramecio.citoplasma.sessions import get_session
from paramecio.citoplasma.urls import redirect, make_url
from paramecio.cromosoma.webmodel import WebModel
from paramecio.libraries.sessions import get_session
from paramecio.libraries.urls import redirect, make_url
from paramecio.libraries.db.webmodel import WebModel
import inspect
class LoginPlugin(object):

View file

@ -1,4 +1,23 @@
#!/usr/bin/env python3
"""
Parameciofm is a series of wrappers for bottle.py, 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/>.
"""
import os
import smtplib
import mimetypes
@ -13,6 +32,8 @@ import ssl as ssl_module
import sys
class SendMail:
"""Class for send email
"""
port=587
@ -25,6 +46,20 @@ class SendMail:
#ssl=True
def __init__(self, ssl=True):
"""Class for send email
Class for send email using standard python library
Attributes:
port (int): The port used for send email, by default 587
host (str): The hostname of mail server used for send the email
username (str): The username for login in mail server
password (str): The password for login in mail server
smtp (smtplib.SMTP): The python SMTP object used for send emails
txt_error: (str): If error, is saved in this attribute
"""
self.smtp=None #smtplib.SMTP(host=self.host, port=self.port)
self.txt_error=''
@ -79,9 +114,9 @@ class SendMail:
return False
except smtplib.SMTPAuthenticationError:
except smtplib.SMTPAuthenticationError as eauth:
self.txt_error='Error: cannot login. Wrong username or password'
self.txt_error='Error: cannot login. Wrong username or password '+eauth.__str__()
return False
@ -94,6 +129,18 @@ class SendMail:
return True
def send(self, from_address, to_address: list, subject, message, content_type='plain', attachments=[]):
""" Method that send email
With this method you can send email to multiple address, html, and add attachments to email
Args:
from_address (str): The adress used for send the email
to_address (list): A list of emails where the email will be sended.
subject (str): The subject of the email
message (str): The content of the message
content_type (str): The type of mail content, can be plain or html.
attachments (list): A list with a serie of file paths for attach to the email.
"""
if self.smtp==None:
if not self.connect():
@ -174,12 +221,14 @@ class SendMail:
return True
def quit(self):
"""Function used when you need quit connection for any reason"""
if self.smtp!=None:
self.smtp.quit()
self.smtp=None
def __del__(self):
"""Method for clean the connection when the object is closed"""
if self.smtp!=None:

View file

@ -0,0 +1,159 @@
from bottle import request, response
from itsdangerous.url_safe import URLSafeTimedSerializer
from paramecio.libraries.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
try:
from settings import config
except:
class config:
cookie_name='paramecio.session'
key_encrypt=create_key_encrypt_256(30)
session_opts={'session.data_dir': 'sessions', 'session.type': 'file', 'session.path': 'paramecio'}
import inspect
class Session(dict):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
self.changed=False
def __setitem__(self, item, value):
super(Session, self).__setitem__(item, value)
self.changed=True
def get_session():
return request.environ.get('session', {})
def session_plugin(callback):
def wrapper(*args, **kwargs):
cookie=request.get_cookie(config.cookie_name)
safe=None
if not cookie:
session=Session()
else:
safe=URLSafeTimedSerializer(config.key_encrypt)
try:
session=Session(safe.loads(cookie))
if type(session).__name__!='Session':
session=Session()
except:
session=Session()
if 'session' in kwargs:
kwargs['session']=session
#For compatibility with old sessions server-side style.
request.environ['session']=session
rv=callback(*args, **kwargs)
if session.changed:
#print('changed')
if not safe:
safe=URLSafeTimedSerializer(config.key_encrypt)
#if not max_age:
response.set_cookie(config.cookie_name, safe.dumps(session), path=config.session_opts['session.path'], httponly=True)
return rv
return wrapper
class SessionPlugin(object):
name = 'session'
api = 2
def __init__(self, keyword='session'):
self.keyword=keyword
def setup(self, app):
''' Make sure that other installed plugins don't affect the same keyword argument.'''
for other in app.plugins:
if not isinstance(other, SessionPlugin): continue
if other.keyword == self.keyword:
raise PluginError("Found another login plugin with "\
"conflicting settings (non-unique keyword).")
def apply(self, callback, context):
# Test if the original callback accepts a 'session' keyword.
# Ignore it if it does not need a login handle.
conf = context.config.get('session') or {}
keyword = conf.get('keyword', self.keyword)
args = inspect.getfullargspec(context.callback)[0]
"""
if keyword not in args:
return callback
"""
def wrapper(*args, **kwargs):
cookie=request.get_cookie(config.cookie_name)
safe=None
if not cookie:
session=Session()
else:
safe=URLSafeTimedSerializer(config.key_encrypt)
try:
session=Session(safe.loads(cookie))
if type(session).__name__!='Session':
session=Session()
except:
session=Session()
#if 'session' in kwargs:
kwargs['session']=session
#For compatibility with old sessions server-side style.
request.environ['session']=session
rv=callback(*args, **kwargs)
if session.changed:
#print('changed')
if not safe:
safe=URLSafeTimedSerializer(config.key_encrypt)
#if not max_age:
response.set_cookie(config.cookie_name, safe.dumps(session), path=config.session_opts['session.path'], httponly=True)
return rv
return wrapper

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
from paramecio.citoplasma.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
from oslo_concurrency import lockutils
from paramecio.libraries.keyutils import create_key_encrypt, create_key_encrypt_256, create_key
#from oslo_concurrency import lockutils
try:
@ -15,7 +15,7 @@ except:
key_encrypt=create_key_encrypt_256(30)
session_opts={'session.data_dir': 'sessions', 'session.type': 'file', 'session.path': 'paramecio'}
from itsdangerous import JSONWebSignatureSerializer
#from itsdangerous import JSONWebSignatureSerializer
from bottle import request, response
import os
try:
@ -142,40 +142,8 @@ elif config.session_opts['session.type']=='redis':
import redis
def load_session(token):
s={}
r=redis.StrictRedis(host=config.session_opts['session.host'], port=config.session_opts['session.port'], db=config.session_opts['session.db'])
value=r.get(token)
if not value:
s={'token': token}
else:
try:
s=json.loads(value.decode('utf-8'))
except:
s={'token': token}
return s
def save_session(token, session):
r=redis.StrictRedis(host=config.session_opts['session.host'], port=config.session_opts['session.port'], db=config.session_opts['session.db'])
r.set(token, json.dumps(session))
def after_session():
pass
else:
def generate_session(session={}, max_age=None):
#secret=URLSafeSerializer(config.key_encrypt)
#session=secret.dumps(session)
token=create_key(30).replace('/', '#')
s={'token': token}
@ -183,9 +151,76 @@ else:
# Bug in python 3.6, if you put max_age how None, is passed to header cookie.
if not max_age:
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'])
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], httponly=True)
else:
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], max_age=max_age)
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], max_age=max_age, httponly=True)
request.environ['session']=s
save_session(token, s)
request.environ['session']=s
return s
def regenerate_session():
token=create_key(30).replace('/', '#')
s={'token': token}
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], httponly=True)
save_session(token, s)
request.environ['session']=s
return ParamecioSession(s)
def load_session(token):
s={}
r=redis.Redis(host=config.session_opts['session.host'], port=config.session_opts['session.port'], decode_responses=True)
value=r.get(token)
r.close()
if not value:
s={'token': token}
else:
try:
s=json.loads(value)
except:
s={'token': token}
return s
def save_session(token, session):
r=redis.Redis(host=config.session_opts['session.host'], port=config.session_opts['session.port'])
r.set(token, json.dumps(session))
r.close()
def after_session():
pass
else:
def generate_session(session={}, max_age=None):
token=create_key(30).replace('/', '#')
s={'token': token}
# Bug in python 3.6, if you put max_age how None, is passed to header cookie.
if not max_age:
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], httponly=True)
else:
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], max_age=max_age, httponly=True)
#Set-Cookie: phango_session=n2ro4lghim75p8vjseb5v3lhap; path=/experiment2/
#response.set_header('Set-Cookie', '%s=%s; path=%s' % (config.cookie_name, token, config.session_opts['session.path']) )
@ -205,7 +240,7 @@ else:
s={'token': token}
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'])
response.set_cookie(config.cookie_name, token, path=config.session_opts['session.path'], httponly=True)
file_session=config.session_opts['session.data_dir']+'/'+token+'_session'
@ -237,7 +272,7 @@ else:
return s
@lockutils.synchronized('not_thread_safe')
#@lockutils.synchronized('not_thread_safe')
def save_session(token, session, create_file=False):
file_session=config.session_opts['session.data_dir']+'/'+token+'_session'

View file

@ -1,9 +1,9 @@
#!/usr/bin/python
from jinja2 import Template, Environment, FileSystemLoader
from paramecio.citoplasma.urls import make_url, make_media_url, make_media_url_module, add_get_parameters
from paramecio.citoplasma.i18n import I18n
from paramecio.citoplasma.sessions import get_session
from paramecio.libraries.urls import make_url, make_media_url, make_media_url_module, add_get_parameters
from paramecio.libraries.i18n import I18n
from paramecio.libraries.sessions import get_session
from settings import config
# Preparing envs for views of modules, and views of

View file

@ -1,4 +1,3 @@
${add_js_home_local('jquery.min.js', 'admin')}
${add_js_home_local('spectrum.js', 'admin')}
${add_css_home_local('spectrum.css', 'admin')}
<%

Some files were not shown because too many files have changed in this diff Show more