diff --git a/.gitignore b/.gitignore index f7b4cec..c7c52a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -credentials.json -.venv/ -*.pyc -__pycache__/ -.env \ No newline at end of file +credentials.json \ No newline at end of file diff --git a/app.py b/app.py index f5b6961..f1071ae 100644 --- a/app.py +++ b/app.py @@ -1,47 +1,11 @@ import os import yaml import gspread -import secrets -import logging -import sys -from flask import Flask, render_template, request, redirect, url_for, abort, session +from flask import Flask, render_template, request, redirect, url_for, abort from datetime import datetime -# Configure logging for Docker/Portainer -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[logging.StreamHandler(sys.stdout)] -) -logger = logging.getLogger(__name__) - app = Flask(__name__) -# Fail-fast secret requirement (allow bypass for pytest testing) -secret_key = os.environ.get('SECRET_KEY') -if not secret_key: - if os.environ.get('TESTING_NO_APPEND') or 'pytest' in sys.modules: - secret_key = 'test_secret_bypassed' - else: - logger.critical("No SECRET_KEY set! Exiting. Set it in .env or Portainer Stack secrets.") - sys.exit(1) - -app.secret_key = secret_key - -def generate_csrf_token(): - if '_csrf_token' not in session: - session['_csrf_token'] = secrets.token_hex(16) - return session['_csrf_token'] - -app.jinja_env.globals['csrf_token'] = generate_csrf_token - -@app.before_request -def csrf_protect(): - if request.method == "POST": - token = session.get('_csrf_token', None) - if not token or token != request.form.get('_csrf_token'): - abort(403) - # Load Configuration def load_config(): with open('events.yaml', 'r') as f: @@ -60,7 +24,7 @@ def get_google_sheet(sheet_id, tab_name=None): # Default to first tab return sh.sheet1 except Exception as e: - logger.error(f"Error connecting to Google Sheet (ID: {sheet_id}, Tab: {tab_name}): {e}") + print(f"Error connecting to Google Sheet (ID: {sheet_id}, Tab: {tab_name}): {e}") return None # NEW: Fetch and filter participants for public display @@ -83,26 +47,26 @@ def get_public_participants(sheet_id, tab_name=None): public_list = [] for row in data_rows: # Ensure row is long enough to avoid errors - if len(row) < 14: + if len(row) < 17: continue # Extract ONLY non-sensitive columns based on our save order # 1: Klasse, 2: Zeilnummer, 3: Bootnaam, 4: Boottype - # 7: Naam, 8: Plaats, 13: Vereniging + # 7: Naam, 10: Plaats, 16: Vereniging entry = { 'klasse': row[1], 'zeilnummer': row[2], 'bootnaam': row[3], 'boottype': row[4], 'naam': row[7], - 'plaats': row[8], - 'vereniging': row[13] + 'plaats': row[10], + 'vereniging': row[16] } public_list.append(entry) return public_list except Exception as e: - logger.error(f"Error fetching participants: {e}") + print(f"Error fetching participants: {e}") return [] @app.route('/') @@ -137,44 +101,34 @@ def event_form(event_slug): event_data['sheet_id'] = sheet_id if request.method == 'POST': - # Basic Validation - required_fields = ['klasse', 'zeilnummer', 'bootnaam', 'naam', 'telefoonmobiel', 'email'] - for field in required_fields: - val = request.form.get(field) - if not val or not val.strip(): - participants = get_public_participants(sheet_id, tab_name) - return render_template('form.html', event=event_data, slug=event_slug, participants=participants, error="Oeps! Bepaalde verplichte velden ontbreken.") - form_data = [ - datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 0: Timestamp + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), # 0: Timestamp request.form.get('klasse'), # 1: Klasse request.form.get('zeilnummer'), # 2: Zeilnummer request.form.get('bootnaam'), # 3: Bootnaam request.form.get('boottype'), # 4: Boottype - ", ".join([k for k in ['genua', 'rolfok', 'spinaker', 'halfwinder', 'genaker', 'alleendacron'] if k in request.form]), # 5: Zeilvoering + ", ".join([k for k in ['genua', 'rolfok', 'spinaker', 'halfwinder', 'genaker', 'dacron'] if k in request.form]), # 5: Zeilvoering request.form.get('schroef'), # 6: Schroef request.form.get('naam'), # 7: Naam (Keep public) - request.form.get('plaats'), # 8: Plaats - request.form.get('land', 'Nederland'), # 9: Land - request.form.get('telefoonmobiel'), # 10: Mobiel (PRIVATE) - request.form.get('email'), # 11: Email (PRIVATE) - request.form.get('startlicentienummer'), # 12: Licentie - request.form.get('vereniging'), # 13: Vereniging - request.form.get('opmerkingen') # 14: Opmerkingen + request.form.get('straat'), # 8: Straat (PRIVATE) + request.form.get('postcode'), # 9: Postcode (PRIVATE) + request.form.get('plaats'), # 10: Plaats + request.form.get('land'), # 11: Land + request.form.get('telefoonmobiel'), # 12: Mobiel (PRIVATE) + request.form.get('telefoonvast'), # 13: Vast (PRIVATE) + request.form.get('email'), # 14: Email (PRIVATE) + request.form.get('startlicentienummer'), # 15: Licentie + request.form.get('vereniging'), # 16: Vereniging + request.form.get('buffet', '0'), # 17: Buffet + request.form.get('ontbijt', '0'), # 18: Ontbijt + request.form.get('opmerkingen') # 19: Opmerkingen ] sheet = get_google_sheet(sheet_id, tab_name) if sheet: - if not os.environ.get('TESTING_NO_APPEND'): - try: - sheet.append_row(form_data) - logger.info(f"Successfully appended registration for {form_data[7]} to {tab_name}") - except Exception as e: - logger.error(f"Failed to append row to {tab_name}: {e}") - return f"Error appending data. Try again later." + sheet.append_row(form_data) return redirect(url_for('success', event_slug=event_slug)) else: - logger.error(f"Could not connect to tab '{tab_name}' during POST.") return f"Error: Could not connect to Google Sheet Tab '{tab_name}'. Check server logs." # GET Request: Fetch participants to show at bottom of form diff --git a/docker-compose.yaml b/docker-compose.yaml index c9c47e2..9cc027f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,8 +4,6 @@ services: build: . container_name: sailing_forms restart: unless-stopped - env_file: - - .env ports: - "5000:5000" volumes: diff --git a/templates/form.html b/templates/form.html index e59bf45..c162aec 100644 --- a/templates/form.html +++ b/templates/form.html @@ -18,14 +18,7 @@

{{ event.title }}

{{ event.description }}

- {% if error %} - - {% endif %} -
-

De Boot

@@ -84,6 +77,14 @@
+
+ + +
+
+ + +
@@ -93,9 +94,13 @@
- +
+
+ + +
@@ -110,6 +115,19 @@
+ +

Eten & Drinken

+
+
+ + +
+
+ + +
+
+
diff --git a/templates/participants.html b/templates/participants.html new file mode 100644 index 0000000..1902b0e --- /dev/null +++ b/templates/participants.html @@ -0,0 +1,43 @@ + + + + + Deelnemerslijst + + + +
+

Huidige Deelnemers

+ ← Terug naar inschrijving + + + + + + + + + + + + + + {% for boat in boats %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
KlasseZeilnummerBootnaamTypeSchipperVereniging
{{ boat.klasse }}{{ boat.zeilnummer }}{{ boat.bootnaam }}{{ boat.boottype }}{{ boat.naam }}{{ boat.vereniging }}
Nog geen inschrijvingen.
+
+ + \ No newline at end of file diff --git a/tests/test_app.py b/tests/test_app.py deleted file mode 100644 index f7e516c..0000000 --- a/tests/test_app.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest -from app import app -from unittest.mock import patch - -@pytest.fixture -def client(): - app.config['TESTING'] = True - with app.test_client() as client: - with client.session_transaction() as sess: - sess['_csrf_token'] = 'test-token' - yield client - -def test_home_page(client): - response = client.get('/') - assert response.status_code == 200 - assert b"Zomeravond" in response.data or b"Papklokken" in response.data - -@patch('app.get_google_sheet') -def test_post_missing_fields(mock_get_sheet, client): - response = client.post('/zomeravond', data={'_csrf_token': 'test-token'}) - assert b"Oeps! Bepaalde verplichte velden ontbreken" in response.data - mock_get_sheet.return_value.append_row.assert_not_called() - -@patch('app.get_google_sheet') -@patch('app.get_public_participants') -def test_post_success(mock_participants, mock_get_sheet, client): - mock_participants.return_value = [] - - data = { - '_csrf_token': 'test-token', - 'klasse': 'Kajuitklasse', - 'zeilnummer': '123', - 'bootnaam': 'TestBoat', - 'naam': 'Test Name', - 'telefoonmobiel': '0612345678', - 'email': 'test@example.com' - } - - response = client.post('/zomeravond', data=data) - assert response.status_code == 302 - assert '/zomeravond/success' in response.headers.get('Location', '') - - # In full testing, append_row is called unless TESTING_NO_APPEND is set. - # But since it's mocked, we can check it. - assert mock_get_sheet.return_value.append_row.called diff --git a/tests/test_e2e.py b/tests/test_e2e.py deleted file mode 100644 index e6b483c..0000000 --- a/tests/test_e2e.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import re -import pytest -from playwright.sync_api import sync_playwright, expect - -def test_homepage_has_title(): - with sync_playwright() as p: - browser = p.chromium.launch() - page = browser.new_page() - page.goto("http://localhost:5000/") - expect(page).to_have_title(re.compile("Zeilwedstrijden")) - browser.close() - -def test_submission_flow(): - with sync_playwright() as p: - browser = p.chromium.launch() - page = browser.new_page() - page.goto("http://localhost:5000/zomeravond") - - # Fill required fields - page.select_option("select[name='klasse']", label="Kajuitklasse") - page.fill("input[name='zeilnummer']", "42") - page.fill("input[name='bootnaam']", "Vliegende Hollander") - page.fill("input[name='naam']", "Hendrik Test") - page.fill("input[name='telefoonmobiel']", "0612345678") - page.fill("input[name='email']", "hendrik@example.com") - - # Accept terms - page.check("input#terms") - - # Submit - page.click("button[type='submit']") - - # Expect success redirect - expect(page).to_have_url(re.compile(r".*/zomeravond/success")) - expect(page.locator("h1")).to_have_text("Bedankt!") - browser.close()