diff --git a/paramecio2/app.py b/paramecio2/app.py index 5bad0da..7a2e7b2 100644 --- a/paramecio2/app.py +++ b/paramecio2/app.py @@ -1,4 +1,4 @@ -from flask import Flask, session, url_for, escape, request, send_file, abort +from flask import Flask, session, url_for, request, send_file, abort from settings import config from importlib import import_module import os @@ -24,11 +24,15 @@ def start_app(): application_root='/' if hasattr(config, 'application_root'): + #print(config.application_root) application_root=config.application_root + app.config.update( APPLICATION_ROOT=application_root ) + + #app.config['APPLICATION_ROOT']=application_root if hasattr(config, 'json_sort_keys'): app.config.update( diff --git a/paramecio2/console.py b/paramecio2/console.py index 6793e62..ea3c9b1 100755 --- a/paramecio2/console.py +++ b/paramecio2/console.py @@ -8,7 +8,7 @@ import re from pathlib import Path from base64 import b64encode from paramecio2.libraries.db.webmodel import WebModel -from paramecio2.modules.admin.models.admin import UserAdmin +from paramecio2.modules.admin.models.admin import UserAdmin, LoginTries from subprocess import call from urllib.parse import urlparse @@ -80,49 +80,7 @@ def start(): print('Error: cannot copy the file index.py to app.py. Check if exists and if you have permissions for this task') - """ - try: - - shutil.copy(workdir+'/frontend/padmin.py', args.path+'/padmin.py') - - except: - - print('Error: cannot copy the file padmin.py. Check if exists and if you have permissions for this task') - - try: - - shutil.copy(workdir+'/frontend/i18nadmin.py', args.path+'/i18nadmin.py') - - except: - - print('Error: cannot copy the file i18nadmin.py. Check if exists and if you have permissions for this task') - - try: - - shutil.copy(workdir+'/frontend/regenerate.py', args.path+'/regenerate.py') - - except: - - print('Error: cannot copy the file regenerate.py. Check if exists and if you have permissions for this task') - - try: - - shutil.copy(workdir+'/frontend/create_module.py', args.path+'/create_module.py') - - except: - - 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') - - 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: os.symlink(workdir, args.path+'/paramecio2', True) @@ -178,6 +136,15 @@ def start(): conf=conf.replace("domain_url='http://localhost:5000'", "domain_url='"+domain_url+"'") + # Question about email + + e_q=input('Email for site: ') + + conf=conf.replace("no-reply@example.com", e_q) + + #if e_q=='': + + #domain_url='http://localhost:8080' with open(path_settings+'/config.py', 'w') as f: @@ -225,6 +192,7 @@ def start(): conn=WebModel.connection() useradmin=UserAdmin(conn) + logintries=LoginTries(conn) # Check if db exists @@ -261,6 +229,9 @@ def start(): print('Error: cannot create table admin, you can create this table with padmin.py') else: + sql_login=logintries.create_table() + logintries.query(sql_login) + # Add admin module to config with open(path_settings+'/config.py', 'r') as f: diff --git a/paramecio2/libraries/datetime.py b/paramecio2/libraries/datetime.py index 9ad9703..e1d6d6f 100644 --- a/paramecio2/libraries/datetime.py +++ b/paramecio2/libraries/datetime.py @@ -664,6 +664,15 @@ class TimeClass: """ return self.t.format(self.format_date_full) + + def format_datetime(self): + """Method for get datetime formatted using format_time attribute + + Returns: + Datetime formatted with format_time attribute + """ + + return self.t.format(self.format_time) def local_to_utc(self): """Method for convert datetime from actual timezone to UTC""" diff --git a/paramecio2/libraries/db/corefields.py b/paramecio2/libraries/db/corefields.py index 43c61ed..16db01f 100644 --- a/paramecio2/libraries/db/corefields.py +++ b/paramecio2/libraries/db/corefields.py @@ -172,6 +172,20 @@ class TextField(PhangoField): 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 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 diff --git a/paramecio2/libraries/db/coreforms.py b/paramecio2/libraries/db/coreforms.py index ebbbb05..caed634 100644 --- a/paramecio2/libraries/db/coreforms.py +++ b/paramecio2/libraries/db/coreforms.py @@ -42,12 +42,13 @@ class BaseForm: 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 diff --git a/paramecio2/libraries/db/extrafields/datetimefield.py b/paramecio2/libraries/db/extrafields/datetimefield.py index 10511d6..c68d028 100644 --- a/paramecio2/libraries/db/extrafields/datetimefield.py +++ b/paramecio2/libraries/db/extrafields/datetimefield.py @@ -26,16 +26,17 @@ class DateTimeField(PhangoField): value=datetime.local_to_gmt(value) elif not datetime.obtain_timestamp(value): - + self.error=True self.txt_error=self.error_default - return '' + + return '0000-00-00 00:00:00' if value==False: self.error=True self.txt_error=self.error_default - return '' + return '0000-00-00 00:00:00' else: """ diff --git a/paramecio2/libraries/db/extrafields/imagefield.py b/paramecio2/libraries/db/extrafields/imagefield.py index f8c58cd..4728b0f 100644 --- a/paramecio2/libraries/db/extrafields/imagefield.py +++ b/paramecio2/libraries/db/extrafields/imagefield.py @@ -5,8 +5,10 @@ from paramecio2.libraries.db.corefields import CharField from paramecio2.libraries.db.extraforms.fileform import FileForm from paramecio2.libraries.keyutils import create_key import traceback +from flask import request +from werkzeug.utils import secure_filename -from bottle import request +# from bottle import request try: from PIL import Image except: @@ -63,61 +65,67 @@ class ImageField(CharField): pass def check(self, value): - + files_uploaded=request.files field_file=self.name+'_file' #if not change - if not field_file in files_uploaded: - - if value=='': + if field_file in files_uploaded: + + if files_uploaded[field_file].filename=='': - if self.model: + if value=='': - if self.model.updated: + if self.model: - old_reset=self.model.yes_reset_conditions - - self.model.yes_reset_conditions=False - - with self.model.select([self.name]) as cur: - - for arr_image in cur: - - if arr_image[self.name]!='': - try: - os.remove(arr_image[self.name]) - except: - pass - - #if arr_image[self.name]!=save_file and arr_image[self.name]!='': - - #value=arr_image[self.name] - - self.model.yes_reset_conditions=old_reset - self.txt_error='Field is empty' - self.error=True - - return '' + if self.model.updated: + + old_reset=self.model.yes_reset_conditions + + self.model.yes_reset_conditions=False + + with self.model.select([self.name]) as cur: + + for arr_image in cur: + + if arr_image[self.name]!='': + try: + os.remove(arr_image[self.name]) + except: + pass + + #if arr_image[self.name]!=save_file and arr_image[self.name]!='': + + #value=arr_image[self.name] + + self.model.yes_reset_conditions=old_reset + self.txt_error='Field is empty' + self.error=True + + return '' - else: - - value=os.path.basename(value) - - return self.save_folder+'/'+value + else: + + value=os.path.basename(value) + + return self.save_folder+'/'+value + else: + value=os.path.basename(value) + + return self.save_folder+'/'+value # Load image file - file_bytecode=files_uploaded[field_file].file + #file_bytecode=files_uploaded[field_file].file - filename=files_uploaded[field_file].filename + filename=secure_filename(files_uploaded[field_file].filename) try: - im=Image.open(file_bytecode) + im=Image.open(files_uploaded[field_file]) except IOError: diff --git a/paramecio2/libraries/db/extrafields/jsonfield.py b/paramecio2/libraries/db/extrafields/jsonfield.py index e90221d..80897ff 100644 --- a/paramecio2/libraries/db/extrafields/jsonfield.py +++ b/paramecio2/libraries/db/extrafields/jsonfield.py @@ -72,6 +72,7 @@ class JsonValueField(PhangoField): super().__init__(name, required) self.error_default='Sorry, the json dict is invalid' + self.default_value={} #self.set_default='NOT NULL' @@ -86,7 +87,7 @@ class JsonValueField(PhangoField): except json.JSONDecodeError: - final_value={} + final_value='{}' self.error=True self.txt_error=self.error_default diff --git a/paramecio2/libraries/generate_admin_class.py b/paramecio2/libraries/generate_admin_class.py index 23101d7..3497132 100644 --- a/paramecio2/libraries/generate_admin_class.py +++ b/paramecio2/libraries/generate_admin_class.py @@ -72,6 +72,8 @@ class GenerateAdminClass: self.url_redirect=self.url + self.pre_update=None + self.post_update=None self.text_home=I18n.lang('common', 'home', 'Home') @@ -131,6 +133,13 @@ class GenerateAdminClass: insert_row=self.model.insert + pre_update_ret=False + + if not self.pre_update: + pre_update_ret=True + else: + pre_update_ret=self.pre_update(self) + try: item_id=str(int(request.args.get('id', '0'))) @@ -149,21 +158,36 @@ class GenerateAdminClass: post=dict(request.form) - if insert_row(post): - flash(I18n.lang('common', 'task_successful', 'Task successful')) - - if self.post_update: - if item_id=='0': - item_id=self.model.insert_id() - self.post_update(self, item_id) - - return redirect(self.url_redirect) + if pre_update_ret: + + if insert_row(post): + flash(I18n.lang('common', 'task_successful', 'Task successful')) + + if self.post_update: + if item_id=='0': + item_id=self.model.insert_id() + self.post_update(self, item_id) + + return redirect(self.url_redirect) + + else: + + url_action=add_get_parameters(self.url, op_admin=2, id=item_id) + + post=dict(request.form) + + form=show_form(post, edit_forms, self.t, True) + + return self.t.load_template(self.template_insert, admin=self, title_edit=title_edit, form=form, model=self.model, id=item_id, url_action=url_action, enctype=self.model.enctype) + + else: url_action=add_get_parameters(self.url, op_admin=2, id=item_id) post=dict(request.form) form=show_form(post, edit_forms, self.t, True) + return self.t.load_template(self.template_insert, admin=self, title_edit=title_edit, form=form, model=self.model, id=item_id, url_action=url_action, enctype=self.model.enctype) diff --git a/paramecio2/libraries/plugins.py b/paramecio2/libraries/plugins.py index 1438355..81d4dd0 100644 --- a/paramecio2/libraries/plugins.py +++ b/paramecio2/libraries/plugins.py @@ -1,4 +1,4 @@ -from flask import g +from flask import g, session, redirect, url_for from functools import wraps from paramecio2.libraries.db.webmodel import WebModel diff --git a/paramecio2/modules/admin/admin/ausers.py b/paramecio2/modules/admin/admin/ausers.py index 644e640..23274da 100644 --- a/paramecio2/modules/admin/admin/ausers.py +++ b/paramecio2/modules/admin/admin/ausers.py @@ -4,6 +4,7 @@ from paramecio2.modules.admin.models.admin import UserAdmin from paramecio2.libraries.generate_admin_class import GenerateAdminClass from paramecio2.libraries.i18n import I18n from paramecio2.libraries.db.coreforms import SelectForm +from paramecio2.libraries.db.coreforms import HiddenForm import copy from paramecio2.modules.admin import admin_app, t as admin_t @@ -22,7 +23,9 @@ def ausers(): user_admin.fields['double_auth'].name_form=SelectForm - user_admin.create_forms(['username', 'password', 'email', 'privileges', 'lang', 'disabled', 'double_auth']) + user_admin.fields['last_login'].name_form=HiddenForm + + user_admin.create_forms(['username', 'password', 'email', 'privileges', 'lang', 'disabled', 'double_auth', 'last_login']) 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')} @@ -43,7 +46,7 @@ def ausers(): admin.list.search_fields=['username'] - admin.arr_fields_edit=['username', 'password', 'repeat_password', 'email', 'lang', 'double_auth', 'disabled'] + admin.arr_fields_edit=['username', 'password', 'repeat_password', 'email', 'lang', 'double_auth', 'disabled', 'last_login'] form_admin=admin.show() diff --git a/paramecio2/modules/admin/app.py b/paramecio2/modules/admin/app.py index 042d3dc..a4755f2 100644 --- a/paramecio2/modules/admin/app.py +++ b/paramecio2/modules/admin/app.py @@ -239,6 +239,8 @@ def login(): #user_admin.set_conditions('WHERE id=%s', [arr_user['id']]).update({'token_auth': token_auth}) + user_admin.fields['token_auth'].protected=False + arr_update['token_auth']=token_auth # Send email @@ -303,7 +305,9 @@ def signup(): forms['privileges']=2 - user_admin.valid_fields=['username', 'email', 'password', 'privileges'] + forms['last_login']=now() + + user_admin.valid_fields=['username', 'email', 'password', 'privileges', 'last_login'] user_admin.create_forms() @@ -357,6 +361,8 @@ def auth_check(): check_csrf() + you_cannot_login=0 + if 'login_admin' in session: code=request.form.get('code', '') @@ -365,23 +371,35 @@ def auth_check(): user_admin.check_user=False - c=user_admin.set_conditions('WHERE id=%s AND token_auth=%s', [session['user_id'], code]).select_count() + arr_user=user_admin.set_conditions('WHERE id=%s', [session.get('user_id', 0)]).select_a_row_where() - if c==1: + if arr_user: - user_admin.safe_query() + if user_admin.fields['token_auth'].verify(code, arr_user['token_auth']): - user_admin.set_conditions('WHERE id=%s', [session['user_id']]).update({'token_auth': ''}) + user_admin.safe_query() + + user_admin.set_conditions('WHERE id=%s', [session['user_id']]).update({'token_auth': ''}) + + session['verify_auth']=True + error=0 + + else: - session['verify_auth']=True - error=0 + you_cannot_login=check_login_tries() + + else: + + you_cannot_login=check_login_tries() - return {'error': error} + return {'error': error, 'you_cannot_login': you_cannot_login} +""" @admin_app.route('/admin/recovery_password/') def recovery_password(): return "" +""" def check_login_tries(): diff --git a/paramecio2/modules/admin/models/admin.py b/paramecio2/modules/admin/models/admin.py index 6b39303..9433fd7 100644 --- a/paramecio2/modules/admin/models/admin.py +++ b/paramecio2/modules/admin/models/admin.py @@ -84,7 +84,7 @@ class UserAdmin(UserModel): self.register(corefields.CharField('token_login')) - self.register(corefields.CharField('token_auth')) + self.register(PasswordField('token_auth')) self.register(PrivilegesField('privileges')) diff --git a/paramecio2/modules/admin/templates/dashboard.phtml b/paramecio2/modules/admin/templates/dashboard.phtml index 2cd227d..eb3b224 100644 --- a/paramecio2/modules/admin/templates/dashboard.phtml +++ b/paramecio2/modules/admin/templates/dashboard.phtml @@ -38,7 +38,8 @@ ${load_js()|n} portal_admin_name_set=('Paramecio', 'Framework!') - #if hasattr(config, 'portal_admin_name_set'): + if hasattr(config, 'portal_admin_name_set'): + portal_admin_name_set=('Paramecio', 'Framework!') %> ${portal_admin_name_set[0]} ${portal_admin_name_set[1]} diff --git a/paramecio2/modules/admin/templates/need_auth.phtml b/paramecio2/modules/admin/templates/need_auth.phtml index 897643d..aedd37b 100644 --- a/paramecio2/modules/admin/templates/need_auth.phtml +++ b/paramecio2/modules/admin/templates/need_auth.phtml @@ -50,7 +50,7 @@ } else { - $('#code_submit').prop('disabled', true); + $('#code_submit').prop('disabled', false); // Firefox have a horrible and stupid bug and you need attr for set de new csrf_token @@ -68,6 +68,12 @@ } + if(data.you_cannot_login) { + + $('#code_error').html("${lang('common', 'error_tries_disabled', 'Error, excessive tries, wait some minutes for login again')}"); + + } + } }); diff --git a/setup.py b/setup.py index f6254b5..ecbec2f 100644 --- a/setup.py +++ b/setup.py @@ -5,15 +5,15 @@ import os from setuptools import setup, find_packages -if sys.version_info < (3, 6): - raise NotImplementedError("Sorry, you need at least Python 3.6 for use paramecio2.") +if sys.version_info < (3, 8): + raise NotImplementedError("Sorry, you need at least Python 3.8 for use paramecio2.") #import paramecio # Pillow should be installed after if you need ImageField # If you install passlib and bcrypt, the password system will use bcrypt by default, if not, will use native crypt libc setup(name='paramecio2', - version='2.0.21', + version='2.0.27', description='Simple Web Framework based in flask and Mako.', long_description='This framework is a simple framework used for create web apps. Paramecio is modular and fast. By default have a module called admin that can be used for create admin sites', author='Antonio de la Rosa Caballero', @@ -38,9 +38,9 @@ setup(name='paramecio2', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Server', 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.6' + 'Programming Language :: Python :: 3.8' + 'Programming Language :: Python :: 3.9' + 'Programming Language :: Python :: 3.10' + 'Programming Language :: Python :: 3.11' ], )