From c9f77b191a4721c960e72696696a49f61d997a76 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Sat, 28 Sep 2024 17:56:58 +0200 Subject: [PATCH 01/38] Fixes in multiple things for modern systems --- paramecio/citoplasma/i18n.py | 89 ++++- paramecio/citoplasma/sessions.py | 2 +- paramecio/create_module.py | 9 +- paramecio/cromosoma/corefields.py | 176 ++++++++- paramecio/cromosoma/coreforms.py | 105 ++++- paramecio/cromosoma/dbadmin.py | 125 +++++- paramecio/cromosoma/extrafields/emailfield.py | 2 +- paramecio/cromosoma/webmodel.py | 362 +++++++++++++++++- paramecio/index.py | 2 +- paramecio/modules/admin/index.py | 2 +- paramecio/settings/config.py.sample | 11 +- paramecio/wsgiapp.py | 2 + setup.py | 23 +- 13 files changed, 829 insertions(+), 81 deletions(-) diff --git a/paramecio/citoplasma/i18n.py b/paramecio/citoplasma/i18n.py index a205934..18c10e5 100644 --- a/paramecio/citoplasma/i18n.py +++ b/paramecio/citoplasma/i18n.py @@ -1,15 +1,38 @@ #!/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 . +""" + from importlib import import_module from paramecio.citoplasma.sessions 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: @@ -26,9 +49,57 @@ def load_lang(*args): # 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'] @@ -42,6 +113,7 @@ class I18n: @staticmethod def get_default_lang(): + """Static method for get the default lang""" lang=I18n.default_lang @@ -53,6 +125,15 @@ class I18n: @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() @@ -67,7 +148,12 @@ class I18n: @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() @@ -93,3 +179,4 @@ class I18n: return json.dumps(arr_final) +common_pgettext=PGetText(__file__) diff --git a/paramecio/citoplasma/sessions.py b/paramecio/citoplasma/sessions.py index 3d41064..257ba14 100644 --- a/paramecio/citoplasma/sessions.py +++ b/paramecio/citoplasma/sessions.py @@ -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: diff --git a/paramecio/create_module.py b/paramecio/create_module.py index 96e7c87..544402f 100644 --- a/paramecio/create_module.py +++ b/paramecio/create_module.py @@ -10,6 +10,8 @@ from settings import config from importlib import import_module def start(): + """Module for create new modules for paramecio + """ parser=argparse.ArgumentParser(description='A tool for create new modules for paramecio') @@ -99,11 +101,8 @@ def regenerate_modules_config(): print("-"*60) exit(1) - f=open('./settings/modules.py', 'w') - - f.write("".join(modules)) - - f.close() + with open('./settings/modules.py', 'w') as f: + f.write("".join(modules)) if __name__=="__main__": start() diff --git a/paramecio/cromosoma/corefields.py b/paramecio/cromosoma/corefields.py index 4c24ba3..5a48807 100644 --- a/paramecio/cromosoma/corefields.py +++ b/paramecio/cromosoma/corefields.py @@ -1,21 +1,46 @@ +""" +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 . +""" + from paramecio.cromosoma.webmodel import PhangoField from paramecio.cromosoma import coreforms from paramecio.citoplasma.i18n import I18n +#from bs4 import BeautifulSoup +import bleach 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): + """ + 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.type_sql='int({})'.format(self.size) def check(self, value): @@ -60,6 +85,11 @@ class BigIntegerField(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 @@ -85,6 +115,7 @@ class FloatField(PhangoField): self.error_default="The value is zero" self.default_value=0 + self.type_sql='float'.format(self.size) def check(self, value): @@ -123,26 +154,55 @@ class FloatField(PhangoField): 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)' 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' 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): @@ -153,19 +213,107 @@ class TextField(PhangoField): return 'TEXT '+self.set_default -class HTMLField(TextField): +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 " + """ + + 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 - return re.sub('<.*?script?>', '', value) + This check method use beautifulsoap for clean and format html code + """ + + # leach.clean('

"trial"

', 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('"', '"') + else: + + return value + + """ + + value=bleach.clean('

"trial"

', tags=self.trusted_tags) + + if self.escape: + + return value.replace('"', '"') + 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) @@ -192,7 +340,7 @@ class ForeignKeyField(IntegerField): value=super().check(value) if value=='0' or value==0: - value='NULL' + value=None return value @@ -206,8 +354,15 @@ class ForeignKeyField(IntegerField): 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 @@ -219,6 +374,8 @@ class BooleanField(IntegerField): self.default_error="Need 0 or 1 value" self.default_value=0 + self.type_sql='tinyint(1)' + def check(self, value): self.error=False @@ -231,6 +388,7 @@ class BooleanField(IntegerField): if value<0 or value>1: self.txt_error=self.default_error self.error=True + value=0 except: diff --git a/paramecio/cromosoma/coreforms.py b/paramecio/cromosoma/coreforms.py index 3247ddc..76d8a7a 100644 --- a/paramecio/cromosoma/coreforms.py +++ b/paramecio/cromosoma/coreforms.py @@ -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 . +""" + + 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 + 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 '' + return '' 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('"', '"').replace("'", ''') 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 '' 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: + + + +
${show_flash_message()|n} <%block name="content"> @@ -96,6 +122,65 @@ ${HeaderHTML.header_home()|n} trigger: 'click' }); + const slider = document.querySelector('input[name="theme"]'); + + slider.addEventListener("change", function () { + + //Block button while send to ajax. + + $(this).prop("disabled",true); + + var dark=''; + + if (this.checked) { + document.body.classList.add("dark"); + + dark='1'; + + } else { + document.body.classList.remove("dark"); + + dark='0'; + } + + $.ajax({ + url: "${make_admin_url('change_theme')}?theme="+dark, + type: 'GET', + data: {}, + success: function (data) { + + if(!data.error) { + + console.log('Changed to dark in all pages'); + + } + else { + + console.log('Cannot set dark theme in all pages!'); + + } + + $(slider).prop("disabled",false); + + }, + error: function (data) { + + alert('Error: '+data.status+' '+data.statusText); + + $(slider).prop("disabled", false); + + }, + dataType: 'json' + }); + + }); + + $(document).ready(function () { + + $('#layer_loading').hide(); + + }); + <%block name="jscript_block"> diff --git a/paramecio/modules/admin/templates/admin/login.phtml b/paramecio/modules/admin/templates/admin/login.phtml index c40f55d..6448c8f 100644 --- a/paramecio/modules/admin/templates/admin/login.phtml +++ b/paramecio/modules/admin/templates/admin/login.phtml @@ -35,8 +35,8 @@ if(data.error==0) { - //window.location.href="${make_url('admin')}"; - location.reload() + window.location.href="${make_url('admin')}"; + //location.reload() } else diff --git a/paramecio/settings/config_admin.py.sample b/paramecio/settings/config_admin.py.sample index 5219e66..e2228c0 100644 --- a/paramecio/settings/config_admin.py.sample +++ b/paramecio/settings/config_admin.py.sample @@ -7,7 +7,7 @@ from importlib import import_module load_lang('paramecio.modules', 'admin') -modules_admin=[[I18n.lang('admin', 'users_admin', 'User\'s Admin'), 'paramecio.modules.admin.admin.ausers', 'ausers']] +modules_admin=[[I18n.lang('admin', 'users_admin', 'User\'s Admin'), 'paramecio.modules.admin.admin.ausers', 'ausers', 'fa-user']] for module in config.modules: module+='.settings.config_admin' From ab57b224e1a2355b0875be2bbb7057e9a7aefad3 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Tue, 1 Oct 2024 01:24:06 +0200 Subject: [PATCH 05/38] Fxes --- paramecio/citoplasma/adminutils.py | 3 ++- paramecio/modules/admin/index.py | 27 +++++++++++++++++-------- paramecio/modules/admin/models/admin.py | 2 ++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/paramecio/citoplasma/adminutils.py b/paramecio/citoplasma/adminutils.py index 323e1d0..cc2d0ae 100644 --- a/paramecio/citoplasma/adminutils.py +++ b/paramecio/citoplasma/adminutils.py @@ -161,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: @@ -183,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 diff --git a/paramecio/modules/admin/index.py b/paramecio/modules/admin/index.py index b869d65..a462c6b 100644 --- a/paramecio/modules/admin/index.py +++ b/paramecio/modules/admin/index.py @@ -199,7 +199,7 @@ def home(module='', submodule='', t=t): user_admin.conditions=['WHERE token_login=%s', [token_login]] - arr_user=user_admin.select_a_row_where(['id', 'privileges']) + arr_user=user_admin.select_a_row_where(['id', 'privileges', 'theme']) if arr_user==False: # delete cookioe @@ -211,6 +211,7 @@ def home(module='', submodule='', t=t): s['id']=arr_user['id'] s['login']=1 s['privileges']=arr_user['privileges'] + s['theme']=str(arr_user['theme']) s.save() @@ -480,15 +481,25 @@ def check_code_token(): @admin_app.get('/change_theme') def change_theme(): - theme_selected=str(request.query.get('theme', '0')) + error=1 - s=get_session() + if check_login(): + + theme_selected=str(request.query.get('theme', '0')) + + s=get_session() + + s['id']=s.get('id', 0) - s['theme']=theme_selected - - s.save() - - error=0 + connection=WebModel.connection() + + s['theme']=theme_selected + + s.save() + + connection.query('update useradmin set theme=%s WHERE id=%s', [s['theme'], s['id']]) + + error=0 return {'error': error} diff --git a/paramecio/modules/admin/models/admin.py b/paramecio/modules/admin/models/admin.py index 3c27276..bcd7e1b 100644 --- a/paramecio/modules/admin/models/admin.py +++ b/paramecio/modules/admin/models/admin.py @@ -89,6 +89,8 @@ class UserAdmin(UserModel): self.register(corefields.IntegerField('num_tries', 1)) + self.register(corefields.IntegerField('theme')) + """ user_admin=WebModel('user_admin') From 6ac5cd8e84fd94d79a613b9be63559866cc43513 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Tue, 1 Oct 2024 18:26:56 +0200 Subject: [PATCH 06/38] Added pyproject --- pyproject.toml | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8c205e6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "paramecio" +authors = [{name = "Antonio de la Rosa", email = "antonio.delarosa@salirdelhoyo.com"}] +readme = "README.md" +dynamic = ["version", "description"] + +classifiers=['Development Status :: 4 - Beta', + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Affero General Public License v3", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", + "Topic :: Internet :: WWW/HTTP :: HTTP Servers", + "Topic :: Internet :: WWW/HTTP :: WSGI", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12" +] +requires-python = ">=3.9" +install_requires=[ + "bottle", + "pymysql", + "sqlalchemy", + "colorama", + "python-slugify", + "mako", + "pillow", + "arrow", + "bleach", + "argon2-cffi", + "oslo.concurrency", + "gunicorn" +] + +[project.urls] +Home = "https://git.cuchulu.com/paramecio/parameciofm/" +Documentation = "https://docs.cuchulu.com/paramecio/" + +[project.scripts] +paramecio2 = "paramecio.console:start" +paramecio2db = "parmecio.cromosoma.dbadmin:start" + +[tool.pytest.ini_options] +testpaths = ["tests"] +filterwarnings = [ + "error", +] From f86a393921ac9d3cce71ffc2a30334e70c38aaa3 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Wed, 2 Oct 2024 01:05:54 +0200 Subject: [PATCH 07/38] Fixes in asuers --- paramecio/modules/admin/admin/ausers.py | 29 ++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/paramecio/modules/admin/admin/ausers.py b/paramecio/modules/admin/admin/ausers.py index f4dcb43..de81825 100644 --- a/paramecio/modules/admin/admin/ausers.py +++ b/paramecio/modules/admin/admin/ausers.py @@ -3,9 +3,15 @@ from paramecio.modules.admin.models.admin import UserAdmin from paramecio.citoplasma.urls import make_url from paramecio.citoplasma.generate_admin_class import GenerateAdminClass -from paramecio.citoplasma.i18n import I18n +from paramecio2.libraries.i18n import I18n, PGetText from paramecio.cromosoma.coreforms import SelectForm +from paramecio.citoplasma.sessions import get_session from settings import config +from bottle import request + +pgettext=PGetText(__file__+'/../') + +_=pgettext.gettext def admin(**args): @@ -17,10 +23,14 @@ def admin(**args): user_admin.fields['privileges'].name_form=SelectForm - user_admin.create_forms(['username', 'password', 'email', 'privileges', 'lang']) + user_admin.fields['theme'].name_form=SelectForm + + user_admin.create_forms(['username', 'password', 'email', 'privileges', 'lang', 'theme']) user_admin.forms['privileges'].arr_select={0: I18n.lang('admin', 'without_privileges', 'Without privileges'), 1: I18n.lang('admin', 'selected_privileges', 'Selected privileges'), 2: I18n.lang('admin', 'administrator', 'Administrator')} + user_admin.forms['theme'].arr_select={0: _('Light theme'), 1: _('Dark theme')} + user_admin.fields['password'].protected=False url=make_url('admin/ausers', {}) @@ -31,7 +41,20 @@ def admin(**args): admin.list.search_fields=['username'] - admin.arr_fields_edit=['username', 'password', 'repeat_password', 'email', 'privileges', 'lang'] + admin.arr_fields_edit=['username', 'password', 'repeat_password', 'email', 'privileges', 'lang', 'theme'] + + #if reqes + if request.environ.get('REQUEST_METHOD', 'POST')=='POST': + s=get_session() + theme=request.forms.get('theme', '0') + + if theme!='0' and theme!='1': + theme='0' + + s['theme']=str(theme) + + s.save() + #admin.list.limit_pages=5 From d20296f0df1e3d5449b55ca884d81fb55c42a581 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Wed, 2 Oct 2024 01:15:06 +0200 Subject: [PATCH 08/38] Fixes in theme --- paramecio/modules/admin/media/css/admin.css | 6 ++++++ paramecio/modules/admin/templates/admin/home.html | 9 +-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/paramecio/modules/admin/media/css/admin.css b/paramecio/modules/admin/media/css/admin.css index e18ddee..175e8bb 100644 --- a/paramecio/modules/admin/media/css/admin.css +++ b/paramecio/modules/admin/media/css/admin.css @@ -811,6 +811,12 @@ a:hover.no_choose_flag } +#logout { + + z-index:99999; + +} + #languages_general { top:8px; diff --git a/paramecio/modules/admin/templates/admin/home.html b/paramecio/modules/admin/templates/admin/home.html index 54de43c..dd69e54 100644 --- a/paramecio/modules/admin/templates/admin/home.html +++ b/paramecio/modules/admin/templates/admin/home.html @@ -58,18 +58,11 @@ ${HeaderHTML.header_home()|n} <%block name="languages">
- <%def name="select_lang(i18n, lang_selected)"> - % if i18n==lang_selected: - choose_flag - % else: - no_choose_flag - % endif - + <%def name="select_lang(i18n, lang_selected)">${'choose_flag' if i18n==lang_selected else 'no_choose_flag'} % if lang_selected!=None: % for i18n in arr_i18n: ${i18n} - % endfor % endif
From ec324087db084d5cc35e43ff0f20d647422992af Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Wed, 2 Oct 2024 19:52:54 +0200 Subject: [PATCH 09/38] Fixes in menu lighting --- paramecio/modules/admin/index.py | 6 +++--- paramecio/modules/admin/templates/admin/home.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/paramecio/modules/admin/index.py b/paramecio/modules/admin/index.py index a462c6b..4b89bb2 100644 --- a/paramecio/modules/admin/index.py +++ b/paramecio/modules/admin/index.py @@ -151,7 +151,7 @@ def home(module='', submodule='', t=t): #args={'t': t, 'connection': connection} content_index=module_imported[module].admin(t=t, connection=connection) - + if t.show_basic_template==True: title_module=menu[module][0] @@ -160,7 +160,7 @@ def home(module='', submodule='', t=t): title_module=content_index[0] content_index=content_index[1] connection.close() - return t.load_template('admin/content.html', title=title_module, content_index=content_index, menu=menu, lang_selected=lang_selected, arr_i18n=I18n.dict_i18n) + return t.load_template('admin/content.html', title=title_module, content_index=content_index, menu=menu, lang_selected=lang_selected, arr_i18n=I18n.dict_i18n, module_name=module) else: connection.close() @@ -170,7 +170,7 @@ def home(module='', submodule='', t=t): else: connection.close() - return t.load_template('admin/index.html', title=I18n.lang('admin', 'welcome_to_paramecio', 'Welcome to Paramecio Admin!!!'), menu=menu, lang_selected=lang_selected, arr_i18n=I18n.dict_i18n) + return t.load_template('admin/index.html', title=I18n.lang('admin', 'welcome_to_paramecio', 'Welcome to Paramecio Admin!!!'), menu=menu, lang_selected=lang_selected, arr_i18n=I18n.dict_i18n, module_name='home') connection.close() return "" diff --git a/paramecio/modules/admin/templates/admin/home.html b/paramecio/modules/admin/templates/admin/home.html index dd69e54..a5d705d 100644 --- a/paramecio/modules/admin/templates/admin/home.html +++ b/paramecio/modules/admin/templates/admin/home.html @@ -74,7 +74,7 @@ ${HeaderHTML.header_home()|n} % for module in menu: % if type(menu[module]).__name__=='list': % if menu[module][0]!="": -
  • ${menu[module][3]|n}${menu[module][0]}
  • +
  • ${menu[module][3]|n}${menu[module][0]}
  • % endif % else: % if menu[module]!="": From f94d6fbd0468e57aed3af3a4cf9119b3e5376e4f Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Wed, 9 Oct 2024 00:19:44 +0200 Subject: [PATCH 10/38] Fix in examples --- paramecio/examples/index.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/paramecio/examples/index.py b/paramecio/examples/index.py index 0237aa8..925668b 100644 --- a/paramecio/examples/index.py +++ b/paramecio/examples/index.py @@ -1,14 +1,14 @@ from paramecio.citoplasma.mtemplates import env_theme, PTemplate from paramecio.citoplasma.urls import make_url -from bottle import route, request +from bottle import request from settings import config +from paramecio.wsgiapp import app env=env_theme(__file__) +t=PTemplate(env) -@route('/example') +@app.route('/example') def home(): - - t=PTemplate(env) return "Hello World!!" From 94d3eef8404db6bc175528c367efb8ac0c9614d6 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Wed, 30 Oct 2024 18:47:51 +0100 Subject: [PATCH 11/38] Many fixes for modernize the codebase --- paramecio/citoplasma/keyutils.py | 67 +++++++++++++++++++ paramecio/citoplasma/sessions.py | 52 +++++++++++++- paramecio/modules/admin/media/css/admin.css | 12 ++-- .../modules/welcome/templates/index.html | 4 +- pyproject.toml | 3 +- setup.py | 2 +- 6 files changed, 127 insertions(+), 13 deletions(-) diff --git a/paramecio/citoplasma/keyutils.py b/paramecio/citoplasma/keyutils.py index 4cb4b8a..5bc3c66 100644 --- a/paramecio/citoplasma/keyutils.py +++ b/paramecio/citoplasma/keyutils.py @@ -1,19 +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 . +""" + 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 + diff --git a/paramecio/citoplasma/sessions.py b/paramecio/citoplasma/sessions.py index 257ba14..97b437c 100644 --- a/paramecio/citoplasma/sessions.py +++ b/paramecio/citoplasma/sessions.py @@ -168,6 +168,52 @@ elif config.session_opts['session.type']=='redis': def after_session(): pass +elif config.session_opts['session.type']=='cookie': + + from itsdangerous.url_safe import URLSafeSerializer + + def generate_session(session : dict ={} , max_age=None): + + #se=UrlSafeSerializer(config.key_encrypt) + + #cookie_value=se.dumps(session) + + request.environ['session']=session + + return session + + def regenerate_session(): + + request.environ['session']={} + + return ParamecioSession({}) + + def load_session(token): + + se=URLSafeSerializer(config.key_encrypt) + + s=se.loads(token) + + return s + + def save_session(token, session, create_file=False): + + cookie=session + + try: + + 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) + + return True + + except: + + return False + + else: def generate_session(session={}, max_age=None): @@ -183,9 +229,9 @@ 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) #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 +251,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' diff --git a/paramecio/modules/admin/media/css/admin.css b/paramecio/modules/admin/media/css/admin.css index 175e8bb..cee2169 100644 --- a/paramecio/modules/admin/media/css/admin.css +++ b/paramecio/modules/admin/media/css/admin.css @@ -11,7 +11,7 @@ body margin:0px; background-color:#f4f6f9; - font-family: "Roboto", sans, sans-serif, serif; + font-family: sans, sans-serif; font-size: 16px; /*-webkit-transition: all 0.5s ease-in-out; transition: all 0.5s ease-in-out; @@ -56,7 +56,7 @@ a:hover { height:52px; color:#000; font-size:22px; - font-family:"Roboto", sans, serif; + font-family:sans, sans-serif; /*background-image:url('../images/background.png'); background-position:top; background-repeat:repeat-x;*/ @@ -78,7 +78,7 @@ a:hover { font-size:28px; padding-left:15px; color: #555555; - font-family:"Roboto", sans, serif; + font-family: sans, serif; /*font-style:italic;*/ /*text-shadow:#000000 1px 1px 1px; filter: progid:DXImageTransform.Microsoft.Shadow(color='#000000', Direction=130, Strength=4);*/ @@ -99,7 +99,7 @@ a:hover { h1, h2 { - font-family:"Roboto", sans, serif; + font-family: sans, sans-serif; border: solid #cbcbcb; border-width: 0px 0px 1px 1px; font-size:26px; @@ -243,7 +243,7 @@ p { padding:5px; font-size:18px; - font-family:"Roboto", sans, serif; + font-family: sans, sans-serif; font-weight:bold; /*background-image:url('../images/background_title.png'); background-position:top; @@ -260,7 +260,7 @@ p { .father_admin { - font-family:"Roboto", sans, serif; + font-family:sans, sans-serif; padding:5px; font-size:18px; display: block; diff --git a/paramecio/modules/welcome/templates/index.html b/paramecio/modules/welcome/templates/index.html index 590b0ed..7047306 100644 --- a/paramecio/modules/welcome/templates/index.html +++ b/paramecio/modules/welcome/templates/index.html @@ -3,7 +3,7 @@ Paramecio WebFramework - +