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] 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: