This commit is contained in:
81
routes.py
81
routes.py
@@ -3,12 +3,12 @@ import os
|
||||
import json
|
||||
import shutil
|
||||
from flask import (
|
||||
Blueprint, render_template, request, redirect, url_for, flash, g, session, current_app
|
||||
Blueprint, render_template, request, redirect, url_for, flash, g, session, current_app, jsonify
|
||||
)
|
||||
from flask_login import login_user, login_required, logout_user, current_user
|
||||
from flask_babel import _
|
||||
from cron_descriptor import ExpressionDescriptor, CasingTypeEnum, Options
|
||||
from werkzeug.utils import secure_filename
|
||||
import gspread
|
||||
|
||||
# --- ИМПОРТ РАСШИРЕНИЙ И МОДЕЛЕЙ ---
|
||||
# Импортируем экземпляры расширений, созданные в app.py
|
||||
@@ -17,7 +17,7 @@ from extensions import db, login_manager, scheduler
|
||||
from models import User, UserConfig
|
||||
from google_sheets import GoogleSheets
|
||||
from request_module import ReqModule
|
||||
from utils import calculate_period_dates, get_dates, generate_template_from_preset, render_temp
|
||||
from utils import calculate_period_dates, get_dates, generate_template_from_preset, render_temp, _parse_cron_string
|
||||
|
||||
|
||||
# --- Создание блюпринта ---
|
||||
@@ -59,14 +59,6 @@ def get_user_upload_path(filename=""):
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
return os.path.join(user_dir, secure_filename(filename))
|
||||
|
||||
def _parse_cron_string(cron_str):
|
||||
"""Парсит строку cron в словарь для APScheduler. Локальная копия для удобства."""
|
||||
parts = cron_str.split()
|
||||
if len(parts) != 5:
|
||||
raise ValueError("Invalid cron string format. Expected 5 parts.")
|
||||
keys = ['minute', 'hour', 'day', 'month', 'day_of_week']
|
||||
return {keys[i]: part for i, part in enumerate(parts)}
|
||||
|
||||
# --- Маршруты ---
|
||||
|
||||
@main_bp.route('/language/<language>')
|
||||
@@ -292,30 +284,38 @@ def mapping_set():
|
||||
config = g.user_config
|
||||
try:
|
||||
new_mappings = {}
|
||||
# Сохраняем существующие настройки расписания при обновлении отчетов
|
||||
current_mappings = config.mappings or {}
|
||||
|
||||
# Итерируемся по всем листам, доступным пользователю в Google Sheets
|
||||
for sheet in config.sheets:
|
||||
# Получаем ID отчета, который пользователь выбрал для этого листа в форме
|
||||
report_key = f"sheet_{sheet['id']}"
|
||||
selected_report_id = request.form.get(report_key)
|
||||
|
||||
# Если пользователь выбрал отчет (а не оставил поле пустым)
|
||||
if selected_report_id:
|
||||
# Получаем существующие данные расписания для этого листа
|
||||
existing_schedule = current_mappings.get(sheet['title'], {})
|
||||
# Инициализируем переменные для расписания
|
||||
schedule_cron = None
|
||||
schedule_period = None
|
||||
|
||||
if isinstance(existing_mapping_value, dict):
|
||||
schedule_cron = existing_mapping_value.get('schedule_cron')
|
||||
schedule_period = existing_mapping_value.get('schedule_period')
|
||||
# Ищем текущие настройки для этого конкретного листа в базе данных
|
||||
current_sheet_mapping = current_mappings.get(sheet.get('title'))
|
||||
|
||||
# Сохраняем новые настройки расписания в новом словаре
|
||||
new_mappings[sheet['title']] = {
|
||||
# Если для этого листа уже есть настройки, и они в новом формате (словарь),
|
||||
# то мы сохраняем его параметры расписания.
|
||||
if isinstance(current_sheet_mapping, dict):
|
||||
schedule_cron = current_sheet_mapping.get('schedule_cron')
|
||||
schedule_period = current_sheet_mapping.get('schedule_period')
|
||||
|
||||
# Создаем новую запись сопоставления.
|
||||
# Она будет содержать НОВЫЙ ID отчета и СТАРЫЕ (сохраненные) настройки расписания.
|
||||
new_mappings[sheet.get('title')] = {
|
||||
'report_id': selected_report_id,
|
||||
'schedule_cron': schedule_cron,
|
||||
'schedule_period': schedule_period
|
||||
}
|
||||
|
||||
# Полностью заменяем старые сопоставления на новые
|
||||
config.mappings = new_mappings
|
||||
db.session.commit()
|
||||
|
||||
@@ -327,12 +327,11 @@ def mapping_set():
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
|
||||
def execute_olap_export(user_id, sheet_title, start_date_str=None, end_date_str=None):
|
||||
def execute_olap_export(app, user_id, sheet_title, start_date_str=None, end_date_str=None):
|
||||
"""
|
||||
Основная логика выгрузки OLAP-отчета. Может вызываться как из эндпоинта, так и из планировщика.
|
||||
Если start_date_str и end_date_str не переданы, вычисляет их на основе расписания.
|
||||
"""
|
||||
app = current_app._get_current_object()
|
||||
with app.app_context():
|
||||
user = db.session.get(User, user_id)
|
||||
if not user:
|
||||
@@ -464,7 +463,7 @@ def render_olap():
|
||||
|
||||
try:
|
||||
# Просто вызываем нашу новую универсальную функцию
|
||||
execute_olap_export(current_user.id, sheet_title, start_date, end_date)
|
||||
execute_olap_export(current_app._get_current_object(), current_user.id, sheet_title, start_date, end_date)
|
||||
flash(_('Report generation task for sheet "%(sheet)s" has been started. The data will appear shortly.', sheet=sheet_title), 'success')
|
||||
except Exception as e:
|
||||
flash(_('An unexpected error occurred: %(error)s', error=str(e)), 'error')
|
||||
@@ -472,6 +471,42 @@ def render_olap():
|
||||
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
@main_bp.route('/translate_cron', methods=['POST'])
|
||||
@login_required
|
||||
def translate_cron():
|
||||
"""
|
||||
Принимает cron-строку и возвращает ее человекочитаемое описание.
|
||||
"""
|
||||
cron_string = request.json.get('cron_string')
|
||||
if not cron_string:
|
||||
return jsonify({'description': ''})
|
||||
|
||||
try:
|
||||
# 1. Получаем и преобразуем код локали
|
||||
app_locale = session.get('language', 'ru')
|
||||
locale_map = { 'ru': 'ru_RU', 'en': 'en_US' }
|
||||
cron_locale = locale_map.get(app_locale, 'en_US')
|
||||
|
||||
# 2. Создаем объект Options ТОЛЬКО для форматирования
|
||||
options = Options()
|
||||
options.locale_code = cron_locale
|
||||
options.casing_type = CasingTypeEnum.Sentence
|
||||
options.use_24hour_time_format = True
|
||||
|
||||
# 3. Создаем ExpressionDescriptor, передавая ему ЯВНО и опции, и локаль
|
||||
descriptor = ExpressionDescriptor(
|
||||
expression=cron_string,
|
||||
options=options
|
||||
)
|
||||
|
||||
# 4. Получаем описание
|
||||
description = descriptor.get_description()
|
||||
|
||||
return jsonify({'description': description})
|
||||
except Exception as e:
|
||||
current_app.logger.warning(f"Cron descriptor failed for string '{cron_string}' with locale '{cron_locale}': {e}")
|
||||
return jsonify({'description': str(_('Invalid cron format'))})
|
||||
|
||||
|
||||
@main_bp.route('/save_schedule', methods=['POST'])
|
||||
@login_required
|
||||
@@ -516,7 +551,7 @@ def save_schedule():
|
||||
id=job_id,
|
||||
func=execute_olap_export,
|
||||
trigger='cron',
|
||||
args=[current_user.id, sheet_title],
|
||||
args=[current_app._get_current_object(), current_user.id, sheet_title],
|
||||
replace_existing=True,
|
||||
**cron_params
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user