feat(calendar): add attachment support to events
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Adds file attachment capability to NordaEvent model (attachment_filename,
attachment_path columns). Admin can upload PDF/DOCX when creating events.
Users see a download link on the event detail page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-18 17:22:18 +01:00
parent 8a1aad948b
commit 327cb5a790
5 changed files with 45 additions and 1 deletions

View File

@ -957,6 +957,19 @@ def admin_calendar_new():
source='manual' source='manual'
) )
# Handle file attachment
attachment = request.files.get('attachment')
if attachment and attachment.filename:
from werkzeug.utils import secure_filename
import uuid
filename = secure_filename(attachment.filename)
stored_name = f"{uuid.uuid4().hex}_{filename}"
upload_dir = os.path.join('static', 'uploads', 'events')
os.makedirs(upload_dir, exist_ok=True)
attachment.save(os.path.join(upload_dir, stored_name))
event.attachment_filename = filename
event.attachment_path = f"static/uploads/events/{stored_name}"
db.add(event) db.add(event)
db.commit() db.commit()

View File

@ -2170,6 +2170,10 @@ class NordaEvent(Base):
# Media # Media
image_url = Column(String(1000)) # Banner/header image URL image_url = Column(String(1000)) # Banner/header image URL
# Attachment
attachment_filename = Column(String(255)) # Original filename
attachment_path = Column(String(1000)) # Server path (static/uploads/events/...)
# Relationships # Relationships
speaker_company = relationship('Company') speaker_company = relationship('Company')
creator = relationship('User', foreign_keys=[created_by]) creator = relationship('User', foreign_keys=[created_by])

View File

@ -0,0 +1,6 @@
-- Migration: 085_event_attachments.sql
-- Description: Add attachment support to norda_events (filename + server path)
-- Date: 2026-03-18
ALTER TABLE norda_events ADD COLUMN IF NOT EXISTS attachment_filename VARCHAR(255);
ALTER TABLE norda_events ADD COLUMN IF NOT EXISTS attachment_path VARCHAR(1000);

View File

@ -103,7 +103,7 @@
</div> </div>
<div class="form-card"> <div class="form-card">
<form method="POST" action="{{ url_for('admin.admin_calendar_new') }}"> <form method="POST" action="{{ url_for('admin.admin_calendar_new') }}" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group"> <div class="form-group">
@ -183,6 +183,12 @@
</div> </div>
</div> </div>
<div class="form-group">
<label for="attachment">Załącznik (PDF, DOCX)</label>
<input type="file" id="attachment" name="attachment" accept=".pdf,.docx,.doc">
<div class="form-hint">Opcjonalny plik do pobrania przez uczestników (np. zaproszenie, agenda)</div>
</div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary">Utworz wydarzenie</button> <button type="submit" class="btn btn-primary">Utworz wydarzenie</button>
<a href="{{ url_for('admin.admin_calendar') }}" class="btn btn-secondary">Anuluj</a> <a href="{{ url_for('admin.admin_calendar') }}" class="btn btn-secondary">Anuluj</a>

View File

@ -508,6 +508,21 @@
</div> </div>
{% endif %} {% endif %}
{% if event.attachment_filename %}
<div style="display: flex; align-items: center; gap: var(--spacing-md); padding: var(--spacing-lg); background: linear-gradient(135deg, #fef3c7, #fefce8); border: 1px solid #fde68a; border-radius: var(--radius-lg); margin-bottom: var(--spacing-xl);">
<svg width="24" height="24" fill="none" stroke="#92400e" stroke-width="2" viewBox="0 0 24 24" style="flex-shrink: 0;">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
</svg>
<div style="flex: 1;">
<div style="font-weight: 600; color: #92400e;">Załącznik</div>
<a href="{{ url_for('static', filename=event.attachment_path.replace('static/', '')) }}" target="_blank" style="color: #b45309; text-decoration: underline;">{{ event.attachment_filename }}</a>
</div>
</div>
{% endif %}
{% if not event.is_past %} {% if not event.is_past %}
{% if event.can_user_attend(current_user) %} {% if event.can_user_attend(current_user) %}
<div class="rsvp-section"> <div class="rsvp-section">