v1
This commit is contained in:
361
routes.py
Normal file
361
routes.py
Normal file
@@ -0,0 +1,361 @@
|
||||
# routes.py
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
from flask import (
|
||||
Blueprint, render_template, request, redirect, url_for, flash, g, session, current_app
|
||||
)
|
||||
from flask_login import login_user, login_required, logout_user, current_user
|
||||
from flask_babel import _
|
||||
from werkzeug.utils import secure_filename
|
||||
import gspread
|
||||
|
||||
# --- ИМПОРТ РАСШИРЕНИЙ И МОДЕЛЕЙ ---
|
||||
# Импортируем экземпляры расширений, созданные в app.py
|
||||
from extensions import db, login_manager
|
||||
# Импортируем наши классы и утилиты
|
||||
from models import User, UserConfig
|
||||
from google_sheets import GoogleSheets
|
||||
from request_module import ReqModule
|
||||
from utils import get_dates, generate_template_from_preset, render_temp
|
||||
|
||||
|
||||
# --- Создание блюпринта ---
|
||||
main_bp = Blueprint('main', __name__)
|
||||
|
||||
|
||||
# --- Регистрация обработчиков для расширений ---
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
"""Загружает пользователя из БД для управления сессией."""
|
||||
return db.session.get(User, int(user_id))
|
||||
|
||||
|
||||
@main_bp.before_app_request
|
||||
def load_user_specific_data():
|
||||
"""Загружает конфигурацию пользователя в глобальный объект `g` для текущего запроса."""
|
||||
g.user_config = None
|
||||
if current_user.is_authenticated:
|
||||
g.user_config = get_user_config()
|
||||
|
||||
|
||||
# --- Вспомогательные функции, специфичные для маршрутов ---
|
||||
|
||||
def get_user_config():
|
||||
"""Получает конфиг для текущего пользователя, создавая его при необходимости."""
|
||||
if not current_user.is_authenticated:
|
||||
return None
|
||||
config = UserConfig.query.filter_by(user_id=current_user.id).first()
|
||||
if not config:
|
||||
config = UserConfig(user_id=current_user.id)
|
||||
db.session.add(config)
|
||||
return config
|
||||
|
||||
def get_user_upload_path(filename=""):
|
||||
"""Возвращает путь для загрузки файлов для текущего пользователя."""
|
||||
if not current_user.is_authenticated:
|
||||
return None
|
||||
user_dir = os.path.join(current_app.config['DATA_DIR'], str(current_user.id))
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
return os.path.join(user_dir, secure_filename(filename))
|
||||
|
||||
|
||||
# --- Маршруты ---
|
||||
|
||||
@main_bp.route('/language/<language>')
|
||||
def set_language(language=None):
|
||||
session['language'] = language
|
||||
return redirect(request.referrer or url_for('.index'))
|
||||
|
||||
@main_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('.index'))
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user is None or not user.check_password(password):
|
||||
flash(_('Invalid username or password'), 'error')
|
||||
return redirect(url_for('.login'))
|
||||
login_user(user, remember=request.form.get('remember'))
|
||||
flash(_('Login successful!'), 'success')
|
||||
next_page = request.args.get('next')
|
||||
return redirect(next_page or url_for('.index'))
|
||||
return render_template('login.html')
|
||||
|
||||
@main_bp.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('.index'))
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username')
|
||||
password = request.form.get('password')
|
||||
if not username or not password:
|
||||
flash(_('Username and password are required.'), 'error')
|
||||
return redirect(url_for('.register'))
|
||||
if User.query.filter_by(username=username).first():
|
||||
flash(_('Username already exists.'), 'error')
|
||||
return redirect(url_for('.register'))
|
||||
|
||||
user = User(username=username)
|
||||
user.set_password(password)
|
||||
user.config = UserConfig()
|
||||
db.session.add(user)
|
||||
try:
|
||||
db.session.commit()
|
||||
flash(_('Registration successful! Please log in.'), 'success')
|
||||
return redirect(url_for('.login'))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(_('An error occurred during registration. Please try again.'), 'error')
|
||||
return redirect(url_for('.register'))
|
||||
|
||||
return render_template('register.html')
|
||||
|
||||
@main_bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
flash(_('You have been logged out.'), 'success')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
@main_bp.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
config = g.user_config
|
||||
return render_template(
|
||||
'index.html',
|
||||
rms_config=config.get_rms_dict(),
|
||||
google_config=config.get_google_dict(),
|
||||
presets=config.presets,
|
||||
sheets=config.sheets,
|
||||
mappings=config.mappings,
|
||||
client_email=config.google_client_email
|
||||
)
|
||||
|
||||
@main_bp.route('/configure_rms', methods=['POST'])
|
||||
@login_required
|
||||
def configure_rms():
|
||||
config = g.user_config
|
||||
try:
|
||||
host = request.form.get('host', '').strip()
|
||||
login = request.form.get('login', '').strip()
|
||||
password = request.form.get('password', '')
|
||||
|
||||
if not config.rms_password and not password:
|
||||
flash(_('Password is required for the first time.'), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
if not host or not login:
|
||||
flash(_('Host and Login fields must be filled.'), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
effective_password = password if password else config.rms_password
|
||||
|
||||
req_module = ReqModule(host, login, effective_password)
|
||||
if req_module.login():
|
||||
presets_data = req_module.take_presets()
|
||||
req_module.logout()
|
||||
|
||||
config.rms_host = host
|
||||
config.rms_login = login
|
||||
if password:
|
||||
config.rms_password = password
|
||||
config.presets = presets_data
|
||||
|
||||
db.session.commit()
|
||||
flash(_('Successfully authorized on RMS server. Received %(num)s presets.', num=len(presets_data)), 'success')
|
||||
else:
|
||||
flash(_('Authorization error on RMS server. Check host, login or password.'), 'error')
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(_('Error configuring RMS: %(error)s', error=str(e)), 'error')
|
||||
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
@main_bp.route('/upload_credentials', methods=['POST'])
|
||||
@login_required
|
||||
def upload_credentials():
|
||||
config = g.user_config
|
||||
if 'cred_file' not in request.files or request.files['cred_file'].filename == '':
|
||||
flash(_('No file was selected.'), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
cred_file = request.files['cred_file']
|
||||
filename = cred_file.filename
|
||||
# Получаем путь для сохранения файла в папке пользователя
|
||||
user_cred_path = get_user_upload_path(filename)
|
||||
temp_path = None
|
||||
|
||||
try:
|
||||
# Сначала сохраняем файл во временную директорию для проверки
|
||||
temp_dir = os.path.join(current_app.config['DATA_DIR'], "temp")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
temp_path = os.path.join(temp_dir, f"temp_{current_user.id}_{filename}")
|
||||
cred_file.save(temp_path)
|
||||
|
||||
with open(temp_path, 'r', encoding='utf-8') as f:
|
||||
cred_data = json.load(f)
|
||||
client_email = cred_data.get('client_email')
|
||||
|
||||
if not client_email:
|
||||
flash(_('Could not find client_email in the credentials file.'), 'error')
|
||||
# Не забываем удалить временный файл при ошибке
|
||||
if os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
# Если все хорошо, перемещаем файл из временной папки в постоянную
|
||||
shutil.move(temp_path, user_cred_path)
|
||||
|
||||
# Сохраняем путь к файлу и email в базу данных
|
||||
config.google_cred_file_path = user_cred_path
|
||||
config.google_client_email = client_email
|
||||
config.sheets = [] # Сбрасываем список листов при смене credentials
|
||||
|
||||
db.session.commit()
|
||||
flash(_('Credentials file successfully uploaded. Email: %(email)s', email=client_email), 'success')
|
||||
|
||||
except json.JSONDecodeError:
|
||||
flash(_('Error: Uploaded file is not a valid JSON.'), 'error')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(_('Error processing credentials: %(error)s', error=str(e)), 'error')
|
||||
finally:
|
||||
# Гарантированно удаляем временный файл, если он еще существует
|
||||
if temp_path and os.path.exists(temp_path):
|
||||
os.remove(temp_path)
|
||||
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
@main_bp.route('/configure_google', methods=['POST'])
|
||||
@login_required
|
||||
def configure_google():
|
||||
config = g.user_config
|
||||
sheet_url = request.form.get('sheet_url', '').strip()
|
||||
|
||||
if not sheet_url:
|
||||
flash(_('Sheet URL must be provided.'), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
config.google_sheet_url = sheet_url
|
||||
|
||||
cred_path = config.google_cred_file_path
|
||||
if not cred_path or not os.path.isfile(cred_path):
|
||||
flash(_('Please upload a valid credentials file first.'), 'warning')
|
||||
config.sheets = []
|
||||
db.session.commit()
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
try:
|
||||
gs_client = GoogleSheets(cred_path, sheet_url)
|
||||
sheets_data = gs_client.get_sheets()
|
||||
config.sheets = sheets_data
|
||||
|
||||
db.session.commit()
|
||||
flash(_('Successfully connected to Google Sheets. Found %(num)s sheets. Settings saved.', num=len(sheets_data)), 'success')
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
config.sheets = []
|
||||
flash(_('Error connecting to Google Sheets: %(error)s. Check the URL and service account permissions.', error=str(e)), 'error')
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception:
|
||||
db.session.rollback()
|
||||
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
@main_bp.route('/mapping_set', methods=['POST'])
|
||||
@login_required
|
||||
def mapping_set():
|
||||
config = g.user_config
|
||||
try:
|
||||
new_mappings = {}
|
||||
for sheet in config.sheets:
|
||||
report_key = f"sheet_{sheet['id']}"
|
||||
selected_report_id = request.form.get(report_key)
|
||||
if selected_report_id:
|
||||
new_mappings[sheet['title']] = selected_report_id
|
||||
|
||||
config.mappings = new_mappings
|
||||
db.session.commit()
|
||||
|
||||
flash(_('Mappings updated successfully.'), 'success')
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(_('Error updating mappings: %(error)s', error=str(e)), 'error')
|
||||
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
@main_bp.route('/render_olap', methods=['POST'])
|
||||
@login_required
|
||||
def render_olap():
|
||||
config = g.user_config
|
||||
sheet_title = None
|
||||
req_module = None
|
||||
|
||||
try:
|
||||
from_date, to_date = get_dates(request.form.get('start_date'), request.form.get('end_date'))
|
||||
sheet_title = next((key for key in request.form if key.startswith('render_')), '').replace('render_', '')
|
||||
if not sheet_title:
|
||||
flash(_('Error: Could not determine which sheet to render the report for.'), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
report_id = config.mappings.get(sheet_title)
|
||||
if not report_id:
|
||||
flash(_('Error: No report is assigned to sheet "%(sheet)s".', sheet=sheet_title), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
if not all([config.rms_host, config.rms_login, config.rms_password, config.google_cred_file_path, config.google_sheet_url]):
|
||||
flash(_('Error: RMS or Google Sheets configuration is incomplete.'), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
preset = next((p for p in config.presets if p.get('id') == report_id), None)
|
||||
if not preset:
|
||||
flash(_('Error: Preset with ID "%(id)s" not found in saved configuration.', id=report_id), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
template = generate_template_from_preset(preset)
|
||||
json_body = render_temp(template, {"from_date": from_date, "to_date": to_date})
|
||||
|
||||
req_module = ReqModule(config.rms_host, config.rms_login, config.rms_password)
|
||||
gs_client = GoogleSheets(config.google_cred_file_path, config.google_sheet_url)
|
||||
|
||||
if req_module.login():
|
||||
result = req_module.take_olap(json_body)
|
||||
|
||||
if 'data' not in result or not isinstance(result['data'], list):
|
||||
flash(_('Error: Unexpected response format from RMS for report "%(name)s".', name=preset.get('name', report_id)), 'error')
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
data_to_insert = []
|
||||
if result['data']:
|
||||
headers = list(result['data'][0].keys())
|
||||
data_to_insert.append(headers)
|
||||
for item in result['data']:
|
||||
data_to_insert.append([item.get(h) for h in headers])
|
||||
|
||||
gs_client.clear_and_write_data(sheet_title, data_to_insert)
|
||||
|
||||
if len(data_to_insert) > 1:
|
||||
flash(_('Report "%(name)s" data successfully written to sheet "%(sheet)s".', name=preset.get('name', report_id), sheet=sheet_title), 'success')
|
||||
else:
|
||||
flash(_('Report "%(name)s" returned no data for the selected period. Sheet "%(sheet)s" has been cleared.', name=preset.get('name', report_id), sheet=sheet_title), 'warning')
|
||||
else:
|
||||
flash(_('Error authorizing on RMS server when trying to get a report.'), 'error')
|
||||
|
||||
except ValueError as ve:
|
||||
flash(_('Data Error: %(error)s', error=str(ve)), 'error')
|
||||
except gspread.exceptions.APIError as api_err:
|
||||
flash(_('Google API Error accessing sheet "%(sheet)s". Check service account permissions.', sheet=sheet_title), 'error')
|
||||
except Exception as e:
|
||||
flash(_('An unexpected error occurred: %(error)s', error=str(e)), 'error')
|
||||
finally:
|
||||
if req_module and req_module.token:
|
||||
req_module.logout()
|
||||
|
||||
return redirect(url_for('.index'))
|
||||
Reference in New Issue
Block a user