Implement Schengen Area Compliance Report
How many close am I to 90 days in the last 180. Fixes #193.
This commit is contained in:
		
							parent
							
								
									084e5f44e3
								
							
						
					
					
						commit
						9c64a9fc99
					
				| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import collections
 | 
					import collections
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					from datetime import date
 | 
				
			||||||
import functools
 | 
					import functools
 | 
				
			||||||
import typing
 | 
					import typing
 | 
				
			||||||
from collections import defaultdict
 | 
					from collections import defaultdict
 | 
				
			||||||
| 
						 | 
					@ -65,6 +66,7 @@ class Trip:
 | 
				
			||||||
    flight_bookings: list[StrDict] = field(default_factory=list)
 | 
					    flight_bookings: list[StrDict] = field(default_factory=list)
 | 
				
			||||||
    name: str | None = None
 | 
					    name: str | None = None
 | 
				
			||||||
    private: bool = False
 | 
					    private: bool = False
 | 
				
			||||||
 | 
					    schengen_compliance: typing.Optional["SchengenCalculation"] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def title(self) -> str:
 | 
					    def title(self) -> str:
 | 
				
			||||||
| 
						 | 
					@ -365,3 +367,37 @@ class Holiday:
 | 
				
			||||||
            if self.local_name and self.local_name != self.name
 | 
					            if self.local_name and self.local_name != self.name
 | 
				
			||||||
            else self.name
 | 
					            else self.name
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class SchengenStay:
 | 
				
			||||||
 | 
					    """Represents a stay in the Schengen area."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    entry_date: date
 | 
				
			||||||
 | 
					    exit_date: typing.Optional[date]  # None if currently in Schengen
 | 
				
			||||||
 | 
					    country: str
 | 
				
			||||||
 | 
					    days: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __post_init__(self) -> None:
 | 
				
			||||||
 | 
					        if self.exit_date is None:
 | 
				
			||||||
 | 
					            # Currently in Schengen, calculate days up to today
 | 
				
			||||||
 | 
					            self.days = (date.today() - self.entry_date).days + 1
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.days = (self.exit_date - self.entry_date).days + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@dataclass
 | 
				
			||||||
 | 
					class SchengenCalculation:
 | 
				
			||||||
 | 
					    """Result of Schengen time calculation."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    total_days_used: int
 | 
				
			||||||
 | 
					    days_remaining: int
 | 
				
			||||||
 | 
					    is_compliant: bool
 | 
				
			||||||
 | 
					    current_180_day_period: tuple[date, date]  # (start, end)
 | 
				
			||||||
 | 
					    stays_in_period: list["SchengenStay"]
 | 
				
			||||||
 | 
					    next_reset_date: typing.Optional[date]  # When the 180-day window resets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def days_over_limit(self) -> int:
 | 
				
			||||||
 | 
					        """Days over the 90-day limit."""
 | 
				
			||||||
 | 
					        return max(0, self.total_days_used - 90)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -351,6 +351,18 @@
 | 
				
			||||||
      <div>Total CO₂: {{ "{:,.1f}".format(total_co2_kg) }} kg</div>
 | 
					      <div>Total CO₂: {{ "{:,.1f}".format(total_co2_kg) }} kg</div>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {% if trip.schengen_compliance %}
 | 
				
			||||||
 | 
					      <div>
 | 
				
			||||||
 | 
					        <strong>Schengen:</strong>
 | 
				
			||||||
 | 
					        {% if trip.schengen_compliance.is_compliant %}
 | 
				
			||||||
 | 
					          <span class="badge bg-success">✅ Compliant</span>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					          <span class="badge bg-danger">❌ Non-compliant</span>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        <span class="text-muted small">({{ trip.schengen_compliance.total_days_used }}/90 days used)</span>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {{ conference_list(trip) }}
 | 
					    {{ conference_list(trip) }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {% for day, elements in trip.elements_grouped_by_day() %}
 | 
					    {% for day, elements in trip.elements_grouped_by_day() %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
  {"endpoint": "weekends", "label": "Weekends" },
 | 
					  {"endpoint": "weekends", "label": "Weekends" },
 | 
				
			||||||
  {"endpoint": "launch_list", "label": "Space launches" },
 | 
					  {"endpoint": "launch_list", "label": "Space launches" },
 | 
				
			||||||
  {"endpoint": "holiday_list", "label": "Holidays" },
 | 
					  {"endpoint": "holiday_list", "label": "Holidays" },
 | 
				
			||||||
 | 
					  {"endpoint": "schengen_report", "label": "Schengen" },
 | 
				
			||||||
  ] + ([{"endpoint": "birthday_list", "label": "Birthdays" }]
 | 
					  ] + ([{"endpoint": "birthday_list", "label": "Birthdays" }]
 | 
				
			||||||
    if g.user.is_authenticated else [])
 | 
					    if g.user.is_authenticated else [])
 | 
				
			||||||
  %}
 | 
					  %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										211
									
								
								templates/schengen_report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								templates/schengen_report.html
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,211 @@
 | 
				
			||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% set heading = "Schengen Area Compliance Report" %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block title %}{{ heading }} - Edward Betts{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container-fluid">
 | 
				
			||||||
 | 
					  <h1>Schengen Area Compliance Report</h1>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  <div class="row">
 | 
				
			||||||
 | 
					    <div class="col-md-8">
 | 
				
			||||||
 | 
					      <div class="card mb-4">
 | 
				
			||||||
 | 
					        <div class="card-header">
 | 
				
			||||||
 | 
					          <h5 class="card-title">Current Status</h5>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="row">
 | 
				
			||||||
 | 
					            <div class="col-md-6">
 | 
				
			||||||
 | 
					              <h6>Compliance Status</h6>
 | 
				
			||||||
 | 
					              {% if current_compliance.is_compliant %}
 | 
				
			||||||
 | 
					                <span class="badge bg-success fs-6">✅ COMPLIANT</span>
 | 
				
			||||||
 | 
					              {% else %}
 | 
				
			||||||
 | 
					                <span class="badge bg-danger fs-6">❌ NON-COMPLIANT</span>
 | 
				
			||||||
 | 
					              {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="col-md-6">
 | 
				
			||||||
 | 
					              <h6>Days Used</h6>
 | 
				
			||||||
 | 
					              <div class="fs-4">{{ current_compliance.total_days_used }}/90</div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          <div class="row mt-3">
 | 
				
			||||||
 | 
					            <div class="col-md-6">
 | 
				
			||||||
 | 
					              {% if current_compliance.is_compliant %}
 | 
				
			||||||
 | 
					                <h6>Days Remaining</h6>
 | 
				
			||||||
 | 
					                <div class="fs-5 text-success">{{ current_compliance.days_remaining }}</div>
 | 
				
			||||||
 | 
					              {% else %}
 | 
				
			||||||
 | 
					                <h6>Days Over Limit</h6>
 | 
				
			||||||
 | 
					                <div class="fs-5 text-danger">{{ current_compliance.days_over_limit }}</div>
 | 
				
			||||||
 | 
					              {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="col-md-6">
 | 
				
			||||||
 | 
					              {% if current_compliance.next_reset_date %}
 | 
				
			||||||
 | 
					                <h6>Next Reset Date</h6>
 | 
				
			||||||
 | 
					                <div class="fs-6">{{ current_compliance.next_reset_date.strftime('%Y-%m-%d') }}</div>
 | 
				
			||||||
 | 
					              {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          <div class="mt-3">
 | 
				
			||||||
 | 
					            <h6>Current 180-day Period</h6>
 | 
				
			||||||
 | 
					            <div class="text-muted">
 | 
				
			||||||
 | 
					              {{ current_compliance.current_180_day_period[0].strftime('%Y-%m-%d') }} to 
 | 
				
			||||||
 | 
					              {{ current_compliance.current_180_day_period[1].strftime('%Y-%m-%d') }}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {% if current_compliance.stays_in_period %}
 | 
				
			||||||
 | 
					      <div class="card mb-4">
 | 
				
			||||||
 | 
					        <div class="card-header">
 | 
				
			||||||
 | 
					          <h5 class="card-title">Stays in Current 180-day Period</h5>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="table-responsive">
 | 
				
			||||||
 | 
					            <table class="table table-striped">
 | 
				
			||||||
 | 
					              <thead>
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                  <th>Entry Date</th>
 | 
				
			||||||
 | 
					                  <th>Exit Date</th>
 | 
				
			||||||
 | 
					                  <th>Country</th>
 | 
				
			||||||
 | 
					                  <th>Days</th>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					              </thead>
 | 
				
			||||||
 | 
					              <tbody>
 | 
				
			||||||
 | 
					                {% for stay in current_compliance.stays_in_period %}
 | 
				
			||||||
 | 
					                <tr>
 | 
				
			||||||
 | 
					                  <td>{{ stay.entry_date.strftime('%Y-%m-%d') }}</td>
 | 
				
			||||||
 | 
					                  <td>
 | 
				
			||||||
 | 
					                    {% if stay.exit_date %}
 | 
				
			||||||
 | 
					                      {{ stay.exit_date.strftime('%Y-%m-%d') }}
 | 
				
			||||||
 | 
					                    {% else %}
 | 
				
			||||||
 | 
					                      <span class="text-muted">ongoing</span>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
 | 
					                  </td>
 | 
				
			||||||
 | 
					                  <td>{{ stay.country | country_flag }} {{ stay.country.upper() }}</td>
 | 
				
			||||||
 | 
					                  <td>{{ stay.days }}</td>
 | 
				
			||||||
 | 
					                </tr>
 | 
				
			||||||
 | 
					                {% endfor %}
 | 
				
			||||||
 | 
					              </tbody>
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {% endif %}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="col-md-4">
 | 
				
			||||||
 | 
					      {% if warnings %}
 | 
				
			||||||
 | 
					      <div class="card mb-4">
 | 
				
			||||||
 | 
					        <div class="card-header">
 | 
				
			||||||
 | 
					          <h5 class="card-title">Warnings</h5>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          {% for warning in warnings %}
 | 
				
			||||||
 | 
					          <div class="alert alert-{{ 'danger' if warning.severity == 'error' else 'warning' }} alert-dismissible fade show" role="alert">
 | 
				
			||||||
 | 
					            <strong>{{ warning.date.strftime('%Y-%m-%d') }}</strong><br>
 | 
				
			||||||
 | 
					            {{ warning.message }}
 | 
				
			||||||
 | 
					            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          {% endfor %}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <div class="card">
 | 
				
			||||||
 | 
					        <div class="card-header">
 | 
				
			||||||
 | 
					          <h5 class="card-title">Compliance History</h5>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="card-body">
 | 
				
			||||||
 | 
					          <div class="chart-container" style="height: 300px;">
 | 
				
			||||||
 | 
					            <canvas id="complianceChart"></canvas>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {% if trips_with_compliance %}
 | 
				
			||||||
 | 
					  <div class="card mt-4">
 | 
				
			||||||
 | 
					    <div class="card-header">
 | 
				
			||||||
 | 
					      <h5 class="card-title">Trip Compliance History</h5>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="card-body">
 | 
				
			||||||
 | 
					      <div class="table-responsive">
 | 
				
			||||||
 | 
					        <table class="table table-striped">
 | 
				
			||||||
 | 
					          <thead>
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					              <th>Trip Date</th>
 | 
				
			||||||
 | 
					              <th>Days Used</th>
 | 
				
			||||||
 | 
					              <th>Days Remaining</th>
 | 
				
			||||||
 | 
					              <th>Status</th>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					          </thead>
 | 
				
			||||||
 | 
					          <tbody>
 | 
				
			||||||
 | 
					            {% for trip_date, calculation in trips_with_compliance.items() %}
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					              <td>{{ trip_date.strftime('%Y-%m-%d') }}</td>
 | 
				
			||||||
 | 
					              <td>{{ calculation.total_days_used }}/90</td>
 | 
				
			||||||
 | 
					              <td>{{ calculation.days_remaining }}</td>
 | 
				
			||||||
 | 
					              <td>
 | 
				
			||||||
 | 
					                {% if calculation.is_compliant %}
 | 
				
			||||||
 | 
					                  <span class="badge bg-success">Compliant</span>
 | 
				
			||||||
 | 
					                {% else %}
 | 
				
			||||||
 | 
					                  <span class="badge bg-danger">Non-compliant</span>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					              </td>
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					            {% endfor %}
 | 
				
			||||||
 | 
					          </tbody>
 | 
				
			||||||
 | 
					        </table>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  {% endif %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					const ctx = document.getElementById('complianceChart').getContext('2d');
 | 
				
			||||||
 | 
					const complianceHistory = {{ compliance_history | tojson }};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const chart = new Chart(ctx, {
 | 
				
			||||||
 | 
					    type: 'line',
 | 
				
			||||||
 | 
					    data: {
 | 
				
			||||||
 | 
					        labels: complianceHistory.map(item => item.date),
 | 
				
			||||||
 | 
					        datasets: [{
 | 
				
			||||||
 | 
					            label: 'Days Used',
 | 
				
			||||||
 | 
					            data: complianceHistory.map(item => item.days_used),
 | 
				
			||||||
 | 
					            borderColor: 'rgb(75, 192, 192)',
 | 
				
			||||||
 | 
					            backgroundColor: 'rgba(75, 192, 192, 0.2)',
 | 
				
			||||||
 | 
					            tension: 0.1
 | 
				
			||||||
 | 
					        }]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					        responsive: true,
 | 
				
			||||||
 | 
					        maintainAspectRatio: false,
 | 
				
			||||||
 | 
					        scales: {
 | 
				
			||||||
 | 
					            y: {
 | 
				
			||||||
 | 
					                beginAtZero: true,
 | 
				
			||||||
 | 
					                max: 90,
 | 
				
			||||||
 | 
					                ticks: {
 | 
				
			||||||
 | 
					                    callback: function(value) {
 | 
				
			||||||
 | 
					                        return value + '/90';
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        plugins: {
 | 
				
			||||||
 | 
					            legend: {
 | 
				
			||||||
 | 
					                display: true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -117,6 +117,25 @@
 | 
				
			||||||
    {% if delta %}
 | 
					    {% if delta %}
 | 
				
			||||||
      <div>How long until trip: {{ delta }}</div>
 | 
					      <div>How long until trip: {{ delta }}</div>
 | 
				
			||||||
    {% endif %}
 | 
					    {% endif %}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    {% if trip.schengen_compliance %}
 | 
				
			||||||
 | 
					      <div class="mt-3">
 | 
				
			||||||
 | 
					        <strong>Schengen Compliance:</strong>
 | 
				
			||||||
 | 
					        {% if trip.schengen_compliance.is_compliant %}
 | 
				
			||||||
 | 
					          <span class="badge bg-success">✅ Compliant</span>
 | 
				
			||||||
 | 
					        {% else %}
 | 
				
			||||||
 | 
					          <span class="badge bg-danger">❌ Non-compliant</span>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        <div class="text-muted small">
 | 
				
			||||||
 | 
					          {{ trip.schengen_compliance.total_days_used }}/90 days used
 | 
				
			||||||
 | 
					          {% if trip.schengen_compliance.is_compliant %}
 | 
				
			||||||
 | 
					            ({{ trip.schengen_compliance.days_remaining }} remaining)
 | 
				
			||||||
 | 
					          {% else %}
 | 
				
			||||||
 | 
					            ({{ trip.schengen_compliance.days_over_limit }} over limit)
 | 
				
			||||||
 | 
					          {% endif %}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {% for item in trip.conferences %}
 | 
					    {% for item in trip.conferences %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										25
									
								
								web_view.py
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								web_view.py
									
									
									
									
									
								
							| 
						 | 
					@ -25,6 +25,7 @@ import agenda.holidays
 | 
				
			||||||
import agenda.stats
 | 
					import agenda.stats
 | 
				
			||||||
import agenda.thespacedevs
 | 
					import agenda.thespacedevs
 | 
				
			||||||
import agenda.trip
 | 
					import agenda.trip
 | 
				
			||||||
 | 
					import agenda.trip_schengen
 | 
				
			||||||
import agenda.utils
 | 
					import agenda.utils
 | 
				
			||||||
from agenda import calendar, format_list_with_ampersand, travel, uk_tz
 | 
					from agenda import calendar, format_list_with_ampersand, travel, uk_tz
 | 
				
			||||||
from agenda.types import StrDict, Trip
 | 
					from agenda.types import StrDict, Trip
 | 
				
			||||||
| 
						 | 
					@ -36,6 +37,13 @@ app.config.from_object("config.default")
 | 
				
			||||||
agenda.error_mail.setup_error_mail(app)
 | 
					agenda.error_mail.setup_error_mail(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.template_filter("country_flag")
 | 
				
			||||||
 | 
					def country_flag_filter(country_code: str) -> str:
 | 
				
			||||||
 | 
					    """Convert country code to emoji flag."""
 | 
				
			||||||
 | 
					    from agenda.schengen import get_country_flag
 | 
				
			||||||
 | 
					    return get_country_flag(country_code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.before_request
 | 
					@app.before_request
 | 
				
			||||||
def handle_auth() -> None:
 | 
					def handle_auth() -> None:
 | 
				
			||||||
    """Handle authentication and set global user."""
 | 
					    """Handle authentication and set global user."""
 | 
				
			||||||
| 
						 | 
					@ -387,11 +395,17 @@ def get_trip_list(
 | 
				
			||||||
    route_distances: agenda.travel.RouteDistances | None = None,
 | 
					    route_distances: agenda.travel.RouteDistances | None = None,
 | 
				
			||||||
) -> list[Trip]:
 | 
					) -> list[Trip]:
 | 
				
			||||||
    """Get list of trips respecting current authentication status."""
 | 
					    """Get list of trips respecting current authentication status."""
 | 
				
			||||||
    return [
 | 
					    trips = [
 | 
				
			||||||
        trip
 | 
					        trip
 | 
				
			||||||
        for trip in agenda.trip.build_trip_list(route_distances=route_distances)
 | 
					        for trip in agenda.trip.build_trip_list(route_distances=route_distances)
 | 
				
			||||||
        if flask.g.user.is_authenticated or not trip.private
 | 
					        if flask.g.user.is_authenticated or not trip.private
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Add Schengen compliance information to each trip
 | 
				
			||||||
 | 
					    for trip in trips:
 | 
				
			||||||
 | 
					        agenda.trip_schengen.add_schengen_compliance_to_trip(trip)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return trips
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route("/trip")
 | 
					@app.route("/trip")
 | 
				
			||||||
| 
						 | 
					@ -527,6 +541,9 @@ def trip_page(start: str) -> str:
 | 
				
			||||||
    prev_trip, trip, next_trip = get_prev_current_and_next_trip(start, trip_list)
 | 
					    prev_trip, trip, next_trip = get_prev_current_and_next_trip(start, trip_list)
 | 
				
			||||||
    if not trip:
 | 
					    if not trip:
 | 
				
			||||||
        flask.abort(404)
 | 
					        flask.abort(404)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Add Schengen compliance information
 | 
				
			||||||
 | 
					    trip = agenda.trip_schengen.add_schengen_compliance_to_trip(trip)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    coordinates = agenda.trip.collect_trip_coordinates(trip)
 | 
					    coordinates = agenda.trip.collect_trip_coordinates(trip)
 | 
				
			||||||
    routes = agenda.trip.get_trip_routes(trip, app.config["PERSONAL_DATA"])
 | 
					    routes = agenda.trip.get_trip_routes(trip, app.config["PERSONAL_DATA"])
 | 
				
			||||||
| 
						 | 
					@ -603,6 +620,12 @@ def trip_stats() -> str:
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route("/schengen")
 | 
				
			||||||
 | 
					def schengen_report() -> str:
 | 
				
			||||||
 | 
					    """Schengen compliance report."""
 | 
				
			||||||
 | 
					    return agenda.trip_schengen.flask_route_schengen_report()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route("/callback")
 | 
					@app.route("/callback")
 | 
				
			||||||
def auth_callback() -> tuple[str, int] | werkzeug.Response:
 | 
					def auth_callback() -> tuple[str, int] | werkzeug.Response:
 | 
				
			||||||
    """Process the authentication callback."""
 | 
					    """Process the authentication callback."""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue