Initial commit: InviteMe event registration app

This commit is contained in:
InviteMe Developer
2026-08-28 12:28:52 +03:00
commit b4e10b8448
23 changed files with 1718 additions and 0 deletions
View File
+60
View File
@@ -0,0 +1,60 @@
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.utils import formataddr
from pathlib import Path
from flask import current_app, render_template
def send_invitation(guest, event_name="Корпоратив", additional_text=""):
"""Отправляет приглашение гостю на email с QR-кодом во вложении."""
config = current_app.config
msg = MIMEMultipart("related")
msg["Subject"] = f"Приглашение на мероприятие: {event_name}"
msg["From"] = formataddr(("InviteMe", config.get("MAIL_DEFAULT_SENDER", config["MAIL_USERNAME"])))
msg["To"] = guest.email
# HTML-шаблон письма
html_body = render_template(
"email/invitation.html",
guest=guest,
event_name=event_name,
additional_text=additional_text,
qr_cid="qr_code_image",
)
msg.attach(MIMEText(html_body, "html", "utf-8"))
# Прикрепляем QR-код
qr_path = Path(config["QR_FOLDER"]) / guest.qr_filename
if qr_path.exists():
with open(qr_path, "rb") as img_file:
img = MIMEImage(img_file.read())
img.add_header("Content-ID", "<qr_code_image>")
img.add_header("Content-Disposition", "inline", filename=guest.qr_filename)
msg.attach(img)
# Отправка через SMTP
server = config["MAIL_SERVER"]
port = config["MAIL_PORT"]
username = config["MAIL_USERNAME"]
password = config["MAIL_PASSWORD"]
use_ssl = config.get("MAIL_USE_SSL", True)
if not username or not password:
raise ValueError("MAIL_USERNAME и MAIL_PASSWORD должны быть указаны в конфигурации")
if use_ssl:
with smtplib.SMTP_SSL(server, port) as smtp:
smtp.login(username, password)
smtp.send_message(msg)
else:
with smtplib.SMTP(server, port) as smtp:
smtp.starttls()
smtp.login(username, password)
smtp.send_message(msg)
guest.invitation_sent = True
return True
+112
View File
@@ -0,0 +1,112 @@
from pathlib import Path
from openpyxl import load_workbook, Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from app import db
from app.models import Guest
REQUIRED_COLUMNS = ["Отдел", "ФИО", "Должность", "e-mail", "название фото"]
def read_guests_from_excel(file_path):
"""Читает список гостей из Excel-файла."""
file_path = Path(file_path)
wb = load_workbook(file_path, data_only=True)
ws = wb.active
# Определяем заголовки
headers = [str(cell.value).strip() if cell.value else "" for cell in ws[1]]
missing = [col for col in REQUIRED_COLUMNS if col not in headers]
if missing:
raise ValueError(f"В файле отсутствуют обязательные столбцы: {', '.join(missing)}")
col_indices = {col: headers.index(col) for col in REQUIRED_COLUMNS}
guests_data = []
for row in ws.iter_rows(min_row=2, values_only=True):
full_name = str(row[col_indices["ФИО"]]).strip() if row[col_indices["ФИО"]] else ""
email = str(row[col_indices["e-mail"]]).strip() if row[col_indices["e-mail"]] else ""
if not full_name or not email:
continue
guests_data.append({
"department": str(row[col_indices["Отдел"]]).strip() if row[col_indices["Отдел"]] else "",
"full_name": full_name,
"position": str(row[col_indices["Должность"]]).strip() if row[col_indices["Должность"]] else "",
"email": email,
"photo_filename": str(row[col_indices["название фото"]]).strip() if row[col_indices["название фото"]] else "",
})
return guests_data
def import_guests_from_excel(file_path):
"""Импортирует гостей из Excel в базу данных."""
guests_data = read_guests_from_excel(file_path)
imported = 0
updated = 0
for data in guests_data:
guest = Guest.query.filter_by(email=data["email"]).first()
if guest:
guest.department = data["department"]
guest.full_name = data["full_name"]
guest.position = data["position"]
guest.photo_filename = data["photo_filename"]
guest.is_active = True
updated += 1
else:
guest = Guest(**data)
db.session.add(guest)
imported += 1
db.session.commit()
return {"imported": imported, "updated": updated, "total": len(guests_data)}
def export_report_to_excel(output_path, event_name="Корпоратив"):
"""Выгружает отчёт о посещаемости в Excel."""
guests = Guest.query.order_by(Guest.department, Guest.full_name).all()
wb = Workbook()
ws = wb.active
ws.title = "Отчёт"
headers = ["Отдел", "ФИО", "Должность", "Email", "Название фото", "Статус", "Время входа"]
ws.append(headers)
# Стили заголовков
for cell in ws[1]:
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
cell.alignment = Alignment(horizontal="center")
for guest in guests:
status = "Пришёл" if guest.is_checked_in else "Не пришёл"
check_in_time = guest.last_check_in.strftime("%d.%m.%Y %H:%M:%S") if guest.last_check_in else ""
ws.append([
guest.department,
guest.full_name,
guest.position or "",
guest.email,
guest.photo_filename or "",
status,
check_in_time,
])
# Автоширина колонок
for column in ws.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
try:
if cell.value:
max_length = max(max_length, len(str(cell.value)))
except Exception:
pass
ws.column_dimensions[column_letter].width = min(max_length + 2, 50)
wb.save(output_path)
return output_path
+37
View File
@@ -0,0 +1,37 @@
import qrcode
from pathlib import Path
from flask import current_app, url_for
def generate_qr_for_guest(guest, base_url=None):
"""Генерирует QR-код для гостя и сохраняет его в uploads/qrcodes/."""
token = guest.token
qr_folder = Path(current_app.config["QR_FOLDER"])
qr_folder.mkdir(parents=True, exist_ok=True)
qr_filename = f"{token}.png"
qr_path = qr_folder / qr_filename
# URL, который будет закодирован в QR
if base_url is None:
try:
check_in_url = url_for("main.check_in", token=token, _external=True)
except Exception:
check_in_url = f"http://localhost:5000/check-in?token={token}"
else:
check_in_url = f"{base_url.rstrip('/')}/check-in?token={token}"
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=4,
)
qr.add_data(check_in_url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save(str(qr_path))
guest.qr_filename = qr_filename
return qr_path, check_in_url