Phase 1 of app.py refactoring - reducing from ~14,455 to ~13,699 lines.
New structure:
- blueprints/reports/ - 4 routes (/raporty/*)
- blueprints/community/contacts/ - 6 routes (/kontakty/*)
- blueprints/community/classifieds/ - 4 routes (/tablica/*)
- blueprints/community/calendar/ - 3 routes (/kalendarz/*)
- utils/ - decorators, helpers, notifications, analytics
- extensions.py - Flask extensions (csrf, login_manager, limiter)
- config.py - environment configurations
Updated templates with blueprint-prefixed url_for() calls.
⚠️ DO NOT DEPLOY before presentation on 2026-01-30 19:00
Tested on DEV: all endpoints working correctly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
182 lines
5.6 KiB
Python
182 lines
5.6 KiB
Python
"""
|
|
Calendar Routes
|
|
===============
|
|
|
|
Public calendar and event registration endpoints.
|
|
"""
|
|
|
|
from datetime import date
|
|
import calendar as cal_module
|
|
from flask import render_template, request, redirect, url_for, flash, jsonify
|
|
from flask_login import login_required, current_user
|
|
|
|
from . import bp
|
|
from database import SessionLocal, NordaEvent, EventAttendee
|
|
|
|
|
|
# Polish month names
|
|
POLISH_MONTHS = {
|
|
1: 'Styczeń', 2: 'Luty', 3: 'Marzec', 4: 'Kwiecień',
|
|
5: 'Maj', 6: 'Czerwiec', 7: 'Lipiec', 8: 'Sierpień',
|
|
9: 'Wrzesień', 10: 'Październik', 11: 'Listopad', 12: 'Grudzień'
|
|
}
|
|
|
|
|
|
@bp.route('/', endpoint='calendar_index')
|
|
@login_required
|
|
def index():
|
|
"""Kalendarz wydarzeń Norda Biznes - widok listy lub siatki miesięcznej"""
|
|
db = SessionLocal()
|
|
try:
|
|
today = date.today()
|
|
|
|
# Parametry widoku
|
|
view_mode = request.args.get('view', 'list') # list lub grid
|
|
year = request.args.get('year', today.year, type=int)
|
|
month = request.args.get('month', today.month, type=int)
|
|
|
|
# Walidacja miesiąca/roku
|
|
if month < 1:
|
|
month = 12
|
|
year -= 1
|
|
elif month > 12:
|
|
month = 1
|
|
year += 1
|
|
|
|
# Oblicz poprzedni/następny miesiąc
|
|
if month == 1:
|
|
prev_month, prev_year = 12, year - 1
|
|
else:
|
|
prev_month, prev_year = month - 1, year
|
|
|
|
if month == 12:
|
|
next_month, next_year = 1, year + 1
|
|
else:
|
|
next_month, next_year = month + 1, year
|
|
|
|
# Dane dla widoku siatki
|
|
month_days = []
|
|
events_by_day = {}
|
|
|
|
if view_mode == 'grid':
|
|
# Pobierz wydarzenia z danego miesiąca
|
|
first_day = date(year, month, 1)
|
|
last_day = date(year, month, cal_module.monthrange(year, month)[1])
|
|
events = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= first_day,
|
|
NordaEvent.event_date <= last_day
|
|
).order_by(NordaEvent.event_date.asc()).all()
|
|
|
|
# Przygotuj strukturę kalendarza (poniedziałek = 0)
|
|
cal = cal_module.Calendar(firstweekday=0)
|
|
month_days = cal.monthdayscalendar(year, month)
|
|
|
|
# Mapuj wydarzenia na dni
|
|
for event in events:
|
|
day = event.event_date.day
|
|
if day not in events_by_day:
|
|
events_by_day[day] = []
|
|
events_by_day[day].append(event)
|
|
|
|
# Dane dla widoku listy (zawsze potrzebne dla fallback)
|
|
upcoming = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= today
|
|
).order_by(NordaEvent.event_date.asc()).all()
|
|
|
|
past = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date < today
|
|
).order_by(NordaEvent.event_date.desc()).limit(5).all()
|
|
|
|
return render_template('calendar/index.html',
|
|
# Dane dla widoku listy
|
|
upcoming_events=upcoming,
|
|
past_events=past,
|
|
today=today,
|
|
# Dane dla widoku siatki
|
|
view_mode=view_mode,
|
|
year=year,
|
|
month=month,
|
|
month_name=POLISH_MONTHS.get(month, ''),
|
|
month_days=month_days,
|
|
events_by_day=events_by_day,
|
|
prev_month=prev_month,
|
|
prev_year=prev_year,
|
|
next_month=next_month,
|
|
next_year=next_year,
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/<int:event_id>', endpoint='calendar_event')
|
|
@login_required
|
|
def event(event_id):
|
|
"""Szczegóły wydarzenia"""
|
|
db = SessionLocal()
|
|
try:
|
|
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
|
if not event:
|
|
flash('Wydarzenie nie istnieje.', 'error')
|
|
return redirect(url_for('calendar_index'))
|
|
|
|
# Sprawdź czy użytkownik jest zapisany
|
|
user_attending = db.query(EventAttendee).filter(
|
|
EventAttendee.event_id == event_id,
|
|
EventAttendee.user_id == current_user.id
|
|
).first()
|
|
|
|
return render_template('calendar/event.html',
|
|
event=event,
|
|
user_attending=user_attending
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/<int:event_id>/rsvp', methods=['POST'], endpoint='calendar_rsvp')
|
|
@login_required
|
|
def rsvp(event_id):
|
|
"""Zapisz się / wypisz z wydarzenia"""
|
|
db = SessionLocal()
|
|
try:
|
|
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
|
if not event:
|
|
return jsonify({'success': False, 'error': 'Wydarzenie nie istnieje'}), 404
|
|
|
|
# Sprawdź czy już zapisany
|
|
existing = db.query(EventAttendee).filter(
|
|
EventAttendee.event_id == event_id,
|
|
EventAttendee.user_id == current_user.id
|
|
).first()
|
|
|
|
if existing:
|
|
# Wypisz
|
|
db.delete(existing)
|
|
db.commit()
|
|
return jsonify({
|
|
'success': True,
|
|
'action': 'removed',
|
|
'message': 'Wypisano z wydarzenia',
|
|
'attendee_count': event.attendee_count
|
|
})
|
|
else:
|
|
# Zapisz
|
|
if event.max_attendees and event.attendee_count >= event.max_attendees:
|
|
return jsonify({'success': False, 'error': 'Brak wolnych miejsc'}), 400
|
|
|
|
attendee = EventAttendee(
|
|
event_id=event_id,
|
|
user_id=current_user.id,
|
|
status='confirmed'
|
|
)
|
|
db.add(attendee)
|
|
db.commit()
|
|
return jsonify({
|
|
'success': True,
|
|
'action': 'added',
|
|
'message': 'Zapisano na wydarzenie',
|
|
'attendee_count': event.attendee_count
|
|
})
|
|
finally:
|
|
db.close()
|