feat / AEB-65 generate reports
This commit is contained in:
BIN
backend/api/utils/fonts/arial_bolditalicmt.ttf
Executable file
BIN
backend/api/utils/fonts/arial_bolditalicmt.ttf
Executable file
Binary file not shown.
BIN
backend/api/utils/fonts/arialmt.ttf
Executable file
BIN
backend/api/utils/fonts/arialmt.ttf
Executable file
Binary file not shown.
143
backend/api/utils/report_generators.py
Normal file
143
backend/api/utils/report_generators.py
Normal file
@@ -0,0 +1,143 @@
|
||||
import csv
|
||||
from io import StringIO, BytesIO
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from django.utils import timezone
|
||||
import os
|
||||
|
||||
FONT_PATH = os.path.join(os.path.dirname(__file__), 'fonts')
|
||||
ARIAL_PATH = os.path.join(FONT_PATH, 'arialmt.ttf')
|
||||
ARIAL_BOLD_PATH = os.path.join(FONT_PATH, 'arial_bolditalicmt.ttf')
|
||||
|
||||
if not os.path.exists(ARIAL_PATH) or not os.path.exists(ARIAL_BOLD_PATH):
|
||||
raise FileNotFoundError(
|
||||
f"Шрифты не найдены. Пожалуйста, убедитесь что файлы arialmt.ttf и arial_bolditalicmt.ttf "
|
||||
f"находятся в директории {FONT_PATH}"
|
||||
)
|
||||
|
||||
pdfmetrics.registerFont(TTFont('Arial', ARIAL_PATH))
|
||||
pdfmetrics.registerFont(TTFont('Arial-Bold', ARIAL_BOLD_PATH))
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomNormal',
|
||||
fontName='Arial',
|
||||
fontSize=10,
|
||||
leading=12,
|
||||
))
|
||||
styles.add(ParagraphStyle(
|
||||
name='CustomHeading1',
|
||||
fontName='Arial-Bold',
|
||||
fontSize=14,
|
||||
leading=16,
|
||||
))
|
||||
|
||||
|
||||
def generate_csv_report(alerts):
|
||||
"""Генерирует CSV отчет из списка алертов"""
|
||||
output = StringIO()
|
||||
writer = csv.writer(output)
|
||||
|
||||
# заголовки
|
||||
headers = ['ID', 'Датчик', 'Объект', 'Значение', 'Тип датчика', 'Сообщение',
|
||||
'Уровень важности', 'Дата создания', 'Обработан']
|
||||
writer.writerow(headers)
|
||||
|
||||
# данные
|
||||
for alert in alerts:
|
||||
zone = alert.sensor.zones.first()
|
||||
object_name = zone.object.title if zone else 'Не указан'
|
||||
|
||||
writer.writerow([
|
||||
alert.id,
|
||||
alert.sensor.name,
|
||||
object_name,
|
||||
f"{alert.metric.value} {alert.sensor.signal_format.unit if alert.sensor.signal_format else ''}" if alert.metric.value else alert.metric.raw_value,
|
||||
alert.sensor_type.name,
|
||||
alert.message,
|
||||
alert.severity,
|
||||
timezone.localtime(alert.created_at).strftime("%Y-%m-%d %H:%M:%S %Z"),
|
||||
'Да' if alert.resolved else 'Нет'
|
||||
])
|
||||
|
||||
return output.getvalue()
|
||||
|
||||
def generate_pdf_report(alerts):
|
||||
"""Генерирует PDF отчет из списка алертов"""
|
||||
buffer = BytesIO()
|
||||
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
||||
elements = []
|
||||
|
||||
# заголовок
|
||||
title = Paragraph(
|
||||
f"Отчет по алертам (сгенерирован {timezone.localtime().strftime('%Y-%m-%d %H:%M %Z')})",
|
||||
styles['CustomHeading1']
|
||||
)
|
||||
elements.append(title)
|
||||
|
||||
# данные для таблицы
|
||||
headers = ['ID', 'Датчик', 'Объект', 'Значение', 'Тип датчика', 'Сообщение',
|
||||
'Уровень', 'Дата создания', 'Обработан']
|
||||
|
||||
# оборачиваем заголовки в параграф
|
||||
header_cells = [Paragraph(str(header), styles['CustomNormal']) for header in headers]
|
||||
data = [header_cells]
|
||||
|
||||
for alert in alerts:
|
||||
zone = alert.sensor.zones.first()
|
||||
object_name = zone.object.title if zone else 'Не указан'
|
||||
|
||||
# оборачиваем каждую ячейку в параграф
|
||||
row_data = [
|
||||
Paragraph(str(alert.id), styles['CustomNormal']),
|
||||
Paragraph(str(alert.sensor.name), styles['CustomNormal']),
|
||||
Paragraph(str(object_name), styles['CustomNormal']),
|
||||
Paragraph(
|
||||
str(f"{alert.metric.value} {alert.sensor.signal_format.unit if alert.sensor.signal_format else ''}" if alert.metric.value else alert.metric.raw_value),
|
||||
styles['CustomNormal']
|
||||
),
|
||||
Paragraph(str(alert.sensor_type.name), styles['CustomNormal']),
|
||||
Paragraph(str(alert.message), styles['CustomNormal']),
|
||||
Paragraph(str(alert.severity), styles['CustomNormal']),
|
||||
Paragraph(timezone.localtime(alert.created_at).strftime("%Y-%m-%d %H:%M:%S %Z"), styles['CustomNormal']),
|
||||
Paragraph('Да' if alert.resolved else 'Нет', styles['CustomNormal'])
|
||||
]
|
||||
data.append(row_data)
|
||||
|
||||
# создаем таблицу
|
||||
table = Table(data)
|
||||
table.setStyle(TableStyle([
|
||||
# стиль заголовка
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#1b8755')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('TOPPADDING', (0, 0), (-1, 0), 12),
|
||||
|
||||
# стиль данных
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 10),
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
|
||||
# отступы для всех ячеек
|
||||
('LEFTPADDING', (0, 0), (-1, -1), 6),
|
||||
('RIGHTPADDING', (0, 0), (-1, -1), 6),
|
||||
('TOPPADDING', (0, 1), (-1, -1), 8),
|
||||
('BOTTOMPADDING', (0, 1), (-1, -1), 8),
|
||||
|
||||
# чередующиеся цвета строк для лучшей читаемости
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.HexColor('#f5f5f5')])
|
||||
]))
|
||||
|
||||
elements.append(table)
|
||||
doc.build(elements)
|
||||
|
||||
return buffer.getvalue()
|
||||
Reference in New Issue
Block a user