Статья Хранение и доступ к скриптам из БД в браузере на Flask

Доброго времени суток, Codeby !

В
первой части я писал про авторизацию веб-приложения, где в роли главной страницы выступала заглушка. Сейчас я решил сделать что-то полезное для себя: у меня достаточно много всяких скриптов, которые рассованы по папкам, флешкам и т.д. Приходится каждый раз копать, но иногда мне нужна только часть кода или какие то функции для быстрого запуска. На рабочем ПК у меня поднят MySQL и я решил хранить код дополнительно в БД, а доступ получать к нему из веб-морды. Зная название скрипта можно будет получить его в браузере и сразу записать в интерпретатор или файл.

Новая модель в БД :
Помимо таблицы users для авторизации, я добавил новую модель (таблицу) в БД:

Python:
class Scripts(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    typeof = db.Column(db.String(1024))
    info = db.Column(db.String(32768))

Чтобы ничего не усложнять тут будет два поля ( не считая id ) - название скрипта и сам код. Надо отметить, что может быть лучше использовать другой тип данных в базе, но я думаю 32728 текстовых символа мне будет пока достаточно.

HTML и роутинг страниц:
Помимо авторизации и регистрации будет два новых маршрута, которые будут возвращать страницы добавления и извлечения записей из БД соответственно.

Python:
@app.route("/", methods=["GET", "POST"])
@login_required
def add_position():
    connect = selecter.create_connection('db', 'user', 'pwd', 'localhost')
    if request.method == 'POST':
        typeof = request.form["typeof"]
        info = request.form["info"]
        print(info.replace('\r\n', '\n'))
        new_position = Scripts(typeof=typeof, info='\n' + info.replace('\r\n', '\n'))
        db.session.add(new_position)
        db.session.commit()
        return redirect(url_for('add_position'))
    return render_template('index.html')

@app.route("/db", methods=["GET", "POST"])
@login_required
def check_position():
    connect = selecter.create_connection('db', 'user', 'pwd', 'localhost')
    if request.method == 'POST':
        datas.clear()
        typeof = request.form["typeof"]
        if typeof:
            if typeof == '*':
                response = selecter.execute_read_query_all(connect)
                for res in response:
                    datas.append('\n' + res[0])
            else:
                response = selecter.execute_read_query(connect, typeof)
                for res in response:
                    datas.append(res[1].replace('\r\n', '<br>'))
    return render_template('database.html', datas=datas)

Доставая данные из базы в цикле надо заменить спец.символы - у SQL с ними возникают проблемы, а заменяя их на тэг <br> можно получить нужные переносы строк в HTML. По команде * можно достать имена всех скриптов, которые хранятся в таблице.

Сам HTML database не содержит ничего особенного, кроме, наверное <pre> тэга, о котором я не знал и который мне очень помог. Обернутые им элементы сохраняют свои первоначальные отступы, это нужно чтобы сохранять питоновские отступы в скриптах из БД. Ну, и к слову, autoescape - элемент шаблонизатора Jinja, который надо вырубить, чтобы превратить тэг <br> в реальный отступ, выводя на веб страницу.

HTML:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>title</title>
    <link href="{{ url_for('static', filename='css/index.css') }}" rel='stylesheet' type='text/css'/>
</head>
<body>
    <H1>MY SCRIPTS</H1>
    <form action="/db" method="POST" class="form">
        <div>
            <label>NAME SCRIPT</label>
            <input name="typeof" placeholder="name script">
          </div>
          <button type="submit">select</button>
    </form>
    <img src="{{ url_for('static', filename='img/auth.png') }}" class="img">
    <div class="scripts">
    <pre>
        {% autoescape false %}
            {% for data in datas %}
                {{data}}
            {% endfor %}
        {% endautoescape %}
    </pre>
    </div>
</body>
</html>


Библиотека для работы с MySQL:
Для дополнительных запросов к БД я использую еще один файлик с библиотекой mysql.connector. Вообще, мне нравится работать с SQL на Python - очень удобно и мало кода. Можно пробежаться ранером сразу по нескольким базам и прогнать запрос. Принцип везде общий - создается объект коннекта, который передается дальше для создания курсора, отправки запроса и получения ответа. Сами запросы прописываются в виде строк и могут принимать переменные.

Python:
import mysql.connector
from mysql.connector import Error

class Selecter:
    def create_connection(self, db_name, db_user, db_password, db_host):
        connection = None
        try:
            connection = mysql.connector.connect(
                database=db_name,
                user=db_user,
                password=db_password,
                host=db_host
            )
            print("Connection to PostgreSQL DB successful")
        except OperationalError as e:
            print(f"The error '{e}' occurred")
        return connection

    def execute_read_query(self, connection, select_data):
        cursor = connection.cursor()
        result = None
        try:
            cursor.execute("SELECT typeof, info FROM scripts WHERE typeof = %s",
                           [select_data])
            result = cursor.fetchall()
            return result
        except OperationalError as e:
            print(f"The error '{e}' occurred")

    def execute_read_query_all(self, connection):
        cursor = connection.cursor()
        result = None
        try:
            cursor.execute("SELECT typeof FROM scripts")
            result = cursor.fetchall()
            return result
        except OperationalError as e:
            print(f"The error '{e}' occurred")

Этот класс используется по /db URL, при клике на кнопку submit создается объект этого класса и выполняется функция execute_read_query, которая принимает на вход переменную (название скрипта) и по ней в базе ищет нужные записи. Основная проблема с которой я столкнулся была в том, чтобы правильно сохранить и вывести данные из базы. Для промежуточного хранения ответа создан массив datas = [ ] из которого шаблонизатор jinja и выбирает данные в цикле внутри database.html


Куда же без черепов :

Люблю я всякие хардкорские штуки ( черепа, розы, паучки :) ) и уж не могу удержаться чтобы куда-нибудь не добавить :D

Screenshot_3.png


И вот так это выглядит. Просто вставляем нужный скрипт в textarea и сохраняем в базу. Так как моих папок развелось ну прям очень много, а среди них есть и тестовые, и не юзабельные, то на рабочем ПК будет удобненько это держать.

Получаем такой вывод :

Screenshot_5.png


Ниже находится весь код. Шрифт конечно большой, зато все как на ладони ). Ну и отступы тоже нормально выводятся, чтобы можно было копировать и сразу выполнить.

Screenshot_7.png



Теория по максимальным значениям SQL:
Инфа с MySQL форума по максимальным значениям, которые могут храниться в БД.
Для справки :

- Максимальное количество таблиц: 4 ГБ
- Максимальный размер таблицы: 32 ТБ
- Столбцы в таблице: 1000
- Максимальный размер строки: n * 4 ГБ
- 8 КБ, если хранятся на той же странице
- n * 4 ГБ с n BLOB-объекты
- Максимальная длина ключа: 3500
- Максимальный размер табличного пространства: 64 ТБ
- Максимальное количество одновременных транзакций: 1023


Спасибо за внимание
Не знаю, на сколько это будет полезно для меня, но в целом было интересно поработать и есть особый кайф, когда ошибки не дают тебе двинуться дальше и ты лихорадочно пытаешься их решить). И весь кайф в том, что тебе все таки удается это сделать и это может относиться не только к коду или к ИТ, а вообще к жизни в целом наверное. В общем, не буду флудить не по теме ( а хочется :) ) и, пожалуй, на этом закончу.

Чекаем название последнего пункта, всем спасибо )
 
Последнее редактирование модератором:

explorer

Platinum
05.08.2018
1 080
2 475
BIT
0
Задумка неплохая, но нужно доработать следующий момент - когда скриптов станет хотя бы несколько десятков, то вряд ли вы будете помнить все их названия. То есть можно сделать ещё одну кнопку просто с выводом названий. Если названия адекватные, то вы уже сразу найдёте нужный скрипт, и уже потом по имени выведите содержимое.

По эксешенам у вас невнятная ошибка на выходе, ведь ошибки могут возникать не только в MySQL. Лучше всего использовать такую конструкцию:

Python:
    except BaseException as err:
        return f"{err.__class__.__name__}: {err}"
 

TR1X

Green Team
04.04.2020
220
156
BIT
127
Задумка неплохая, но нужно доработать следующий момент - когда скриптов станет хотя бы несколько десятков, то вряд ли вы будете помнить все их названия. То есть можно сделать ещё одну кнопку просто с выводом названий. Если названия адекватные, то вы уже сразу найдёте нужный скрипт, и уже потом по имени выведите содержимое.

По эксешенам у вас невнятная ошибка на выходе, ведь ошибки могут возникать не только в MySQL. Лучше всего использовать такую конструкцию:

Python:
    except BaseException as err:
        return f"{err.__class__.__name__}: {err}"
Поправил это, круто что вы заметили, честно говоря я тоже думал об этом, но не впилил сразу) И спасибо за инфу по эксепшенам.
 
  • Нравится
Реакции: explorer

explorer

Platinum
05.08.2018
1 080
2 475
BIT
0
Поправил это, круто что вы заметили, честно говоря я тоже думал об этом, но не впилил сразу) И спасибо за инфу по эксепшенам.
Добавлю на случай если не совсем понятно. Блок try/except нужно писать в route, а не в импортируемых файлах, и эксепшн соответственно в route. Тогда у вас будут ловиться именно все возможные ошибки, а не только mysql.
 

TR1X

Green Team
04.04.2020
220
156
BIT
127
Добавлю на случай если не совсем понятно. Блок try/except нужно писать в route, а не в импортируемых файлах, и эксепшн соответственно в route. Тогда у вас будут ловиться именно все возможные ошибки, а не только mysql.
Понятно, ну так то там особо замудреных циклов нет, ломаться нечему особо по идее)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!