├── __init__.py
├── bot
├── __init__.py
├── states
│ ├── __init__.py
│ └── SQLAlchemyStorage.py
├── handlers
│ ├── channels
│ │ └── __init__.py
│ ├── groups
│ │ └── __init__.py
│ ├── errors
│ │ ├── __init__.py
│ │ └── error_handler.py
│ ├── users
│ │ ├── __init__.py
│ │ ├── help.py
│ │ ├── start.py
│ │ └── echo.py
│ └── __init__.py
├── keyboards
│ ├── default
│ │ └── __init__.py
│ ├── inline
│ │ ├── __init__.py
│ │ └── callback_datas.py
│ └── __init__.py
├── utils
│ ├── misc
│ │ ├── __init__.py
│ │ └── throttling.py
│ ├── __init__.py
│ ├── set_bot_commands.py
│ └── notify_admins.py
├── filters
│ └── __init__.py
├── middlewares
│ ├── __init__.py
│ └── throttling.py
├── loader.py
├── main.py
├── texts.py
└── .gitignore
├── server
├── __init__.py
├── model_views
│ ├── AdminConfig.py
│ ├── BotSettingsView.py
│ ├── HiddenModelView.py
│ ├── __init__.py
│ ├── OrdersModelView.py
│ ├── mixins
│ │ └── AuthMixin.py
│ ├── TextsModelView.py
│ ├── AdminModelView.py
│ ├── CKEditorModelView.py
│ └── HomeView.py
├── templates
│ ├── home.html
│ ├── text_model_view.html
│ ├── static
│ │ └── js
│ │ │ ├── home_chart.js
│ │ │ └── bot_settings.js
│ ├── bot_settings_view.html
│ ├── base.html
│ └── login.html
├── auth.py
└── main.py
├── .env
├── database
├── __init__.py
├── models
│ ├── __init__.py
│ ├── texts.py
│ ├── product.py
│ ├── order.py
│ ├── admin.py
│ └── user.py
├── db_config.py
└── loader.py
├── daemon.py
├── config.py
├── requirements.txt
├── README.md
└── .gitignore
/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bot/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bot/states/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | BOT_SECRET_TOKEN="TOKEN_HERE"
--------------------------------------------------------------------------------
/bot/handlers/channels/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bot/handlers/groups/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bot/keyboards/default/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bot/keyboards/inline/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/database/__init__.py:
--------------------------------------------------------------------------------
1 | from .loader import db
--------------------------------------------------------------------------------
/bot/keyboards/inline/callback_datas.py:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/bot/handlers/errors/__init__.py:
--------------------------------------------------------------------------------
1 | from . import error_handler
2 |
--------------------------------------------------------------------------------
/bot/utils/misc/__init__.py:
--------------------------------------------------------------------------------
1 | from .throttling import rate_limit
2 |
--------------------------------------------------------------------------------
/bot/keyboards/__init__.py:
--------------------------------------------------------------------------------
1 | from . import default
2 | from . import inline
3 |
--------------------------------------------------------------------------------
/bot/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from . import misc
2 | from .notify_admins import on_startup_notify
3 |
--------------------------------------------------------------------------------
/bot/handlers/users/__init__.py:
--------------------------------------------------------------------------------
1 | from . import echo
2 | from . import help
3 | from . import start
4 |
--------------------------------------------------------------------------------
/server/model_views/AdminConfig.py:
--------------------------------------------------------------------------------
1 |
2 | class Categories:
3 | MANAGEMENT = 'Management'
4 |
5 |
6 |
--------------------------------------------------------------------------------
/bot/handlers/__init__.py:
--------------------------------------------------------------------------------
1 | from . import channels
2 | from . import errors
3 | from . import groups
4 | from . import users
5 |
--------------------------------------------------------------------------------
/bot/filters/__init__.py:
--------------------------------------------------------------------------------
1 | # from .is_admin import AdminFilter
2 |
3 |
4 | if __name__ == "filters":
5 | #dp.filters_factory.bind(is_admin)
6 | pass
7 |
--------------------------------------------------------------------------------
/database/models/__init__.py:
--------------------------------------------------------------------------------
1 | from .admin import AdminUser
2 | from .order import Order
3 | from .product import Product
4 | from .texts import Texts
5 | from .user import User
6 |
--------------------------------------------------------------------------------
/bot/middlewares/__init__.py:
--------------------------------------------------------------------------------
1 | from bot.loader import dp
2 | from .throttling import ThrottlingMiddleware
3 |
4 |
5 | if __name__ == "middlewares":
6 | dp.middleware.setup(ThrottlingMiddleware())
7 |
--------------------------------------------------------------------------------
/bot/loader.py:
--------------------------------------------------------------------------------
1 | from aiogram import Bot, Dispatcher, types
2 |
3 | from bot.states.SQLAlchemyStorage import SQLAlchemyStorage
4 | from config import BOT_TOKEN
5 |
6 | bot = Bot(token=BOT_TOKEN, parse_mode=types.ParseMode.HTML)
7 | storage = SQLAlchemyStorage()
8 | dp = Dispatcher(bot, storage=storage)
9 |
--------------------------------------------------------------------------------
/bot/utils/set_bot_commands.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 |
3 |
4 | async def set_default_commands(dp):
5 | await dp.bot.set_my_commands(
6 | [
7 | types.BotCommand("start", "Запустить бота"),
8 | types.BotCommand("help", "Вывести справку"),
9 | ]
10 | )
11 |
--------------------------------------------------------------------------------
/server/model_views/BotSettingsView.py:
--------------------------------------------------------------------------------
1 | from flask_admin import BaseView, expose
2 |
3 | from server.model_views.mixins.AuthMixin import AuthMixin
4 |
5 |
6 | class BotSettingsView(AuthMixin, BaseView):
7 | @expose('/')
8 | def index(self):
9 | return self.render('bot_settings_view.html')
10 |
--------------------------------------------------------------------------------
/bot/utils/notify_admins.py:
--------------------------------------------------------------------------------
1 | from aiogram import Dispatcher
2 | from loguru import logger
3 |
4 | from config import ADMINS
5 |
6 |
7 | async def on_startup_notify(dp: Dispatcher):
8 | for admin in ADMINS:
9 | try:
10 | await dp.bot.send_message(admin, "Bot was started")
11 |
12 | except Exception as err:
13 | logger.error(err)
14 |
--------------------------------------------------------------------------------
/server/model_views/HiddenModelView.py:
--------------------------------------------------------------------------------
1 | from flask_admin.contrib.sqla import ModelView
2 |
3 | from server.model_views.mixins.AuthMixin import AuthMixin
4 |
5 |
6 | class HiddenModelView(AuthMixin, ModelView):
7 |
8 | def is_visible(self):
9 | return False
10 |
11 | def __init__(self, model, session, **kwargs):
12 | super(HiddenModelView, self).__init__(model, session, **kwargs)
--------------------------------------------------------------------------------
/server/model_views/__init__.py:
--------------------------------------------------------------------------------
1 | from .AdminModelView import AdminModelView
2 | from .BotSettingsView import BotSettingsView
3 | from .CKEditorModelView import CKEditorModelView
4 | from .HiddenModelView import HiddenModelView
5 | from .OrdersModelView import OrderModelView
6 | from .TextsModelView import TextsModelView
7 |
8 | __all__ = [BotSettingsView, OrderModelView, CKEditorModelView, HiddenModelView]
9 |
10 |
--------------------------------------------------------------------------------
/server/model_views/OrdersModelView.py:
--------------------------------------------------------------------------------
1 | from flask_admin.contrib.sqla import ModelView
2 |
3 | from database.models import Order
4 | from server.model_views.mixins.AuthMixin import AuthMixin
5 |
6 |
7 | class OrderModelView(AuthMixin, ModelView):
8 | form_excluded_columns = ('product_id')
9 |
10 | def __init__(self, session, **kwargs):
11 | super(OrderModelView, self).__init__(Order, session, **kwargs)
12 |
--------------------------------------------------------------------------------
/bot/handlers/users/help.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 | from aiogram.dispatcher.filters.builtin import CommandHelp
3 |
4 | from bot.loader import dp
5 |
6 |
7 | @dp.message_handler(CommandHelp())
8 | async def bot_help(message: types.Message):
9 | text = ("Список команд: ",
10 | "/start - Начать диалог",
11 | "/help - Получить справку")
12 |
13 | await message.answer("\n".join(text))
14 |
--------------------------------------------------------------------------------
/database/db_config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from config import DEBUG, LOCAL_DATABASE_URL, DATABASE_URL, USE_LOCAL_VARIABLES
4 | basedir = os.path.abspath(os.path.dirname(__file__))
5 |
6 |
7 | class Config(object):
8 | DEBUG = DEBUG
9 | TESTING = False
10 | CSRF_ENABLED = True
11 | SQLALCHEMY_DATABASE_URI = LOCAL_DATABASE_URL if USE_LOCAL_VARIABLES else DATABASE_URL
12 | SQLALCHEMY_TRACK_MODIFICATIONS = False
13 |
--------------------------------------------------------------------------------
/server/templates/home.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% block body %}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {% endblock %}
--------------------------------------------------------------------------------
/bot/utils/misc/throttling.py:
--------------------------------------------------------------------------------
1 | def rate_limit(limit: int, key=None):
2 | """
3 | Decorator for configuring rate limit and key in different functions.
4 |
5 | :param limit:
6 | :param key:
7 | :return:
8 | """
9 |
10 | def decorator(func):
11 | setattr(func, 'throttling_rate_limit', limit)
12 | if key:
13 | setattr(func, 'throttling_key', key)
14 | return func
15 |
16 | return decorator
17 |
--------------------------------------------------------------------------------
/server/model_views/mixins/AuthMixin.py:
--------------------------------------------------------------------------------
1 | from flask import url_for
2 | from flask_login import current_user
3 | from werkzeug.utils import redirect
4 |
5 |
6 | class AuthMixin(object):
7 | def is_accessible(self):
8 | if current_user.is_authenticated:
9 | return True
10 | return False
11 |
12 | def _handle_view(self, name, **kwargs):
13 | if not self.is_accessible():
14 | return redirect(url_for('auth.login'))
15 |
--------------------------------------------------------------------------------
/bot/main.py:
--------------------------------------------------------------------------------
1 | from aiogram import executor
2 |
3 | from bot.loader import dp
4 | from bot.utils.notify_admins import on_startup_notify
5 | from bot.utils.set_bot_commands import set_default_commands
6 |
7 |
8 | async def on_startup(dispatcher):
9 | await set_default_commands(dispatcher)
10 | await on_startup_notify(dispatcher)
11 |
12 |
13 | def bot_init():
14 | executor.start_polling(dp, on_startup=on_startup)
15 |
16 |
17 | if __name__ == '__main__':
18 | bot_init()
19 |
--------------------------------------------------------------------------------
/server/templates/text_model_view.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/model/edit.html' %}
2 |
3 | {% block tail %}
4 | {{ super() }}
5 |
6 | {{ ckeditor.config(name='value', custom_config="uiColor: '#e9eff0', enterMode: 2,
7 | fillEmptyBlocks: false, disallowedContent: 'div(*)  ',
8 | allowedContent: true,
9 | toolbarGroups: [{ name: 'links' }, { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }]") }}
10 | {% endblock %}
--------------------------------------------------------------------------------
/database/models/texts.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String
2 |
3 | from database.loader import db
4 |
5 |
6 | class Texts(db.Model):
7 | __tablename__ = 'texts'
8 | id = Column(Integer, primary_key=True)
9 |
10 | name = Column(String(length=80), nullable=False)
11 | value = Column(db.UnicodeText)
12 |
13 | def __init__(self, name, value="empty text"):
14 | self.name = name
15 | self.value = value
16 |
17 |
18 | if __name__ == '__main__':
19 | db.create_all()
20 | db.session.commit()
21 |
--------------------------------------------------------------------------------
/daemon.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from time import sleep
3 |
4 | from loguru import logger
5 |
6 |
7 | class DaemonConfig:
8 | OPERATING_HOURS = (0,)
9 | ERROR_COOLDOWN_TIME = 5 * 60
10 | COOLDOWN_TIME = 60 * 60
11 |
12 |
13 | def daemon_init():
14 | while True:
15 | if datetime.now().hour in DaemonConfig.OPERATING_HOURS:
16 | try:
17 | pass
18 | except Exception as error:
19 | logger.error(str(error))
20 | sleep(DaemonConfig.ERROR_COOLDOWN_TIME)
21 | else:
22 | sleep(DaemonConfig.COOLDOWN_TIME)
23 |
--------------------------------------------------------------------------------
/server/model_views/TextsModelView.py:
--------------------------------------------------------------------------------
1 | import flask
2 | import requests
3 | from loguru import logger
4 |
5 | from .CKEditorModelView import CKEditorModelView
6 | from .mixins.AuthMixin import AuthMixin
7 |
8 |
9 | class TextsModelView(AuthMixin, CKEditorModelView):
10 | def after_model_change(self, form, model, is_created):
11 | if not is_created: # Model was updated
12 | try:
13 | ans = requests.post(flask.request.url_root + 'restart_bot')
14 | logger.info(ans.text)
15 | except Exception as error:
16 | logger.error(f"Bot restart error: {error}")
17 |
--------------------------------------------------------------------------------
/database/models/product.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import Column, Integer, String, Float
2 | from sqlalchemy.orm import relationship
3 |
4 | from database.loader import db
5 |
6 |
7 | class Product(db.Model):
8 | id = Column(Integer, primary_key=True)
9 |
10 | name = Column(String(50))
11 | description = Column(String(200))
12 | weight = Column(Float)
13 | price = Column(Integer)
14 | available_quantity = Column(Integer, default=10)
15 | image_url = Column(String(200), nullable=True)
16 |
17 | orders = relationship('Order', backref='product')
18 |
19 | def __str__(self):
20 | return self.name
21 |
--------------------------------------------------------------------------------
/bot/handlers/users/start.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 | from aiogram.dispatcher.filters.builtin import CommandStart
3 |
4 | from bot.loader import dp
5 | from bot.texts import _
6 | from database.models import User
7 |
8 |
9 | @dp.message_handler(CommandStart())
10 | async def bot_start(message: types.Message):
11 | referer_id = ''
12 | command_args = message.text.split('start ')
13 | if len(command_args) > 1:
14 | referer_id = command_args[1]
15 | User.register(message.from_user, referer_id=referer_id, chat_id=message.chat.id)
16 |
17 | await message.answer(_('start_text').format(message.from_user.full_name))
18 |
--------------------------------------------------------------------------------
/database/models/order.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | from sqlalchemy import Column, Integer, DateTime, ForeignKey
4 |
5 | from database.loader import db
6 |
7 |
8 | class Order(db.Model):
9 | id = Column(Integer, primary_key=True)
10 |
11 | product_id = Column(Integer, ForeignKey('product.id'))
12 | was_created = Column(DateTime(), default=datetime.datetime.now())
13 |
14 | user_id = Column(Integer, ForeignKey('user.id'))
15 |
16 | def __init__(self, name):
17 | self.name = name
18 |
19 | def __str__(self):
20 | return f'{self.user.alias if self.user else ""} {self.product}'
21 |
22 | def __repr__(self):
23 | return self.__str__()
24 |
--------------------------------------------------------------------------------
/bot/handlers/users/echo.py:
--------------------------------------------------------------------------------
1 | from aiogram import types
2 | from aiogram.dispatcher import FSMContext
3 |
4 | from bot.loader import dp
5 |
6 |
7 | @dp.message_handler(state=None)
8 | async def bot_echo(message: types.Message):
9 | await message.answer(f"{message.text}"
10 | f"Вы были зарегестрированы {None}")
11 |
12 |
13 | @dp.message_handler(state="*", content_types=types.ContentTypes.ANY)
14 | async def bot_echo_all(message: types.Message, state: FSMContext):
15 | state = await state.get_state()
16 | await message.answer(f"Состояние {state}.\n"
17 | f"\nСообщение:\n"
18 | f"{message}\n")
19 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dotenv import load_dotenv
4 |
5 | load_dotenv() # take environment variables from .env.
6 |
7 | DEBUG = os.getenv('DEBUG', 'False') == 'True'
8 | USE_LOCAL_VARIABLES = os.getenv('USE_LOCAL_VARIABLES', 'True') == 'True'
9 |
10 | # bot
11 | BOT_TOKEN = os.getenv('BOT_SECRET_TOKEN')
12 | ADMINS = [492621220]
13 |
14 | # server
15 | PORT = int(os.environ.get("PORT", 5000))
16 | PRODUCTION_HOST = '0.0.0.0'
17 | LOCAL_HOST = '127.0.0.1'
18 |
19 | # main admin data
20 | ADMIN_EMAIL = os.getenv('ADMIN_PASSWORD', 'admin@admin')
21 | ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD', 'admin')
22 |
23 | # database
24 | LOCAL_DATABASE_URL = 'sqlite:///Main.db'
25 | DATABASE_URL = os.getenv('DATABASE_URL')
26 |
--------------------------------------------------------------------------------
/database/loader.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from flask import Flask
4 | from flask_sqlalchemy import SQLAlchemy
5 | from loguru import logger
6 |
7 | from database.db_config import Config
8 |
9 | if os.path.exists('../server/templates'):
10 | templates_folder = os.path.abspath('../server/templates')
11 | else:
12 | # to run with command
13 | templates_folder = os.path.abspath('./server/templates')
14 |
15 | logger.info(f"Admin templates folder: {templates_folder}")
16 | app = Flask(__name__, template_folder=templates_folder, static_folder=templates_folder + '/static', )
17 | app.config.from_object(Config)
18 | db = SQLAlchemy(app)
19 |
20 | if __name__ == '__main__':
21 | # db.drop_all()
22 | db.create_all()
23 | db.session.commit()
24 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiogram==2.13
2 | aiohttp==3.7.4.post0
3 | alembic==1.6.5
4 | async-timeout==3.0.1
5 | asyncpg==0.23.0
6 | attrs==21.2.0
7 | Babel==2.9.1
8 | certifi==2021.5.30
9 | chardet==4.0.0
10 | click==8.0.1
11 | colorama==0.4.4
12 | Flask==2.0.1
13 | Flask-Admin==1.5.8
14 | Flask-CKEditor==0.4.6
15 | Flask-Login==0.5.0
16 | Flask-Migrate==3.0.1
17 | Flask-SQLAlchemy==2.5.1
18 | greenlet==1.1.0
19 | idna==2.10
20 | itsdangerous==2.0.1
21 | Jinja2==3.0.1
22 | Mako==1.1.4
23 | MarkupSafe==2.0.1
24 | multidict==5.1.0
25 | python-dateutil==2.8.1
26 | python-editor==1.0.4
27 | pytz==2021.1
28 | requests==2.25.1
29 | six==1.16.0
30 | SQLAlchemy==1.3.24
31 | typing-extensions==3.10.0.0
32 | urllib3==1.26.5
33 | Werkzeug==2.0.1
34 | WTForms==2.3.3
35 | yarl==1.6.3
36 |
37 | loguru~=0.5.3
38 | python-dotenv~=0.19.0
--------------------------------------------------------------------------------
/server/model_views/AdminModelView.py:
--------------------------------------------------------------------------------
1 | from flask_admin.contrib.sqla import ModelView
2 | from flask_login import current_user
3 |
4 | from database.models import AdminUser
5 | from server.model_views.mixins.AuthMixin import AuthMixin
6 |
7 |
8 | class AdminModelView(AuthMixin, ModelView):
9 | form_excluded_columns = ('password',)
10 | column_exclude_list = ('password',)
11 |
12 | def __init__(self, session, **kwargs):
13 | super(AdminModelView, self).__init__(AdminUser, session, **kwargs)
14 |
15 | @property
16 | def can_create(self):
17 | return current_user.is_super_admin
18 |
19 | @property
20 | def can_edit(self):
21 | return current_user.is_super_admin
22 |
23 | @property
24 | def can_delete(self):
25 | return current_user.is_super_admin and AdminUser.query.filter_by(is_super_admin=True).count() > 1
26 |
--------------------------------------------------------------------------------
/server/model_views/CKEditorModelView.py:
--------------------------------------------------------------------------------
1 | from flask_admin.contrib.sqla import ModelView
2 | from wtforms import TextAreaField
3 | from wtforms.widgets import TextArea
4 |
5 |
6 | class CKTextAreaWidget(TextArea):
7 | def __call__(self, field, **kwargs):
8 | if kwargs.get('class'):
9 | kwargs['class'] += " ckeditor"
10 | else:
11 | kwargs.setdefault('class', 'ckeditor')
12 | return super(CKTextAreaWidget, self).__call__(field, **kwargs)
13 |
14 |
15 | class CKTextAreaField(TextAreaField):
16 | widget = CKTextAreaWidget()
17 |
18 |
19 | class CKEditorModelView(ModelView):
20 | column_list = ['id', 'value']
21 | can_delete = False
22 | can_create = False
23 | form_overrides = dict(value=CKTextAreaField)
24 |
25 | create_template = 'text_model_view.html'
26 | edit_template = 'text_model_view.html'
27 |
--------------------------------------------------------------------------------
/server/templates/static/js/home_chart.js:
--------------------------------------------------------------------------------
1 | let context = $("#lineChart").get(0).getContext('2d');
2 |
3 | let chartData = $('#data').data('chart_data')
4 |
5 | let chart = new Chart(context, {
6 | type: 'line',
7 | data: {
8 | labels: chartData.labels,
9 | datasets: [
10 | {
11 | label: "registrations",
12 | data: chartData.registrationsCount,
13 | fill: false,
14 | lineTension: 0.2,
15 | backgroundColor: "#27ab4f"
16 | },
17 | {
18 | label: "orders",
19 | data: chartData.ordersCount,
20 | fill: false,
21 | lineTension: 0.2,
22 | backgroundColor: "#9fe817",
23 | },
24 | ]
25 | },
26 | options: {
27 | responsive: true
28 | },
29 | })
--------------------------------------------------------------------------------
/database/models/admin.py:
--------------------------------------------------------------------------------
1 | from flask_login import UserMixin
2 | from sqlalchemy import Column, Integer, String, Boolean
3 | from werkzeug.security import generate_password_hash
4 |
5 | from database.loader import db
6 |
7 |
8 | class AdminUser(UserMixin, db.Model):
9 | """
10 | Admin panel authentication model
11 | """
12 | id = Column(Integer, primary_key=True)
13 | email = Column(String(100), unique=True)
14 | password = Column(String(100))
15 | name = Column(String(100))
16 | is_super_admin = Column(Boolean(), default=False)
17 |
18 | @staticmethod
19 | def register(email, name, password, is_super_admin=False):
20 | new_user = AdminUser(email=email, password=generate_password_hash(password, method='sha256'),
21 | is_super_admin=is_super_admin, name=name)
22 | db.session.add(new_user)
23 | db.session.commit()
24 | return new_user
25 |
--------------------------------------------------------------------------------
/server/templates/bot_settings_view.html:
--------------------------------------------------------------------------------
1 | {% extends 'admin/master.html' %}
2 | {% block body %}
3 |
4 |
5 |
|
|