298 lines
9.1 KiB
Python
298 lines
9.1 KiB
Python
import calendar
|
||
from collections import defaultdict
|
||
from datetime import datetime, time, timedelta
|
||
|
||
from django.contrib import messages
|
||
from django.contrib.auth.decorators import login_required
|
||
from django.core.exceptions import PermissionDenied
|
||
from django.shortcuts import get_object_or_404, redirect, render
|
||
from django.urls import reverse
|
||
from django.utils import timezone
|
||
|
||
from .forms import EventForm
|
||
from .models import Event
|
||
|
||
|
||
PROJECT_NAME = 'Roadshow Calendar'
|
||
DEFAULT_META_DESCRIPTION = 'Track where your business will be each day with a polished public calendar and secure staff-only event management.'
|
||
|
||
|
||
def _parse_month(month_value: str | None):
|
||
today = timezone.localdate()
|
||
if month_value:
|
||
try:
|
||
parsed = datetime.strptime(month_value, '%Y-%m').date()
|
||
return parsed.replace(day=1)
|
||
except ValueError:
|
||
pass
|
||
return today.replace(day=1)
|
||
|
||
|
||
def _next_month(month_start):
|
||
return (month_start.replace(day=28) + timedelta(days=4)).replace(day=1)
|
||
|
||
|
||
def _previous_month(month_start):
|
||
return (month_start - timedelta(days=1)).replace(day=1)
|
||
|
||
|
||
def _build_month_context(month_value=None):
|
||
month_start = _parse_month(month_value)
|
||
next_month = _next_month(month_start)
|
||
tz = timezone.get_current_timezone()
|
||
range_start = timezone.make_aware(datetime.combine(month_start, time.min), tz)
|
||
range_end = timezone.make_aware(datetime.combine(next_month, time.min), tz)
|
||
|
||
events = list(
|
||
Event.objects.filter(is_published=True, start__lt=range_end, end__gte=range_start).order_by('start', 'name')
|
||
)
|
||
|
||
event_map = defaultdict(list)
|
||
for event in events:
|
||
start_local = timezone.localtime(event.start)
|
||
end_local = timezone.localtime(event.end)
|
||
current_day = start_local.date()
|
||
final_day = end_local.date()
|
||
while current_day <= final_day:
|
||
event_map[current_day.isoformat()].append(
|
||
{
|
||
'name': event.name,
|
||
'location': event.location,
|
||
'time': f"{start_local:%I:%M %p} – {end_local:%I:%M %p}" if start_local.date() == end_local.date() else f"{start_local:%b %d, %I:%M %p} – {end_local:%b %d, %I:%M %p}",
|
||
'summary': event.summary,
|
||
'event_url': event.event_url,
|
||
'detail_url': reverse('event_detail', kwargs={'slug': event.slug}),
|
||
}
|
||
)
|
||
current_day += timedelta(days=1)
|
||
|
||
month_calendar = calendar.Calendar(firstweekday=6)
|
||
today = timezone.localdate()
|
||
calendar_weeks = []
|
||
for week in month_calendar.monthdatescalendar(month_start.year, month_start.month):
|
||
week_days = []
|
||
for day in week:
|
||
iso = day.isoformat()
|
||
day_events = event_map.get(iso, [])
|
||
week_days.append(
|
||
{
|
||
'date': day,
|
||
'iso': iso,
|
||
'day': day.day,
|
||
'in_month': day.month == month_start.month,
|
||
'is_today': day == today,
|
||
'event_count': len(day_events),
|
||
'has_events': bool(day_events),
|
||
}
|
||
)
|
||
calendar_weeks.append(week_days)
|
||
|
||
return {
|
||
'calendar_weeks': calendar_weeks,
|
||
'calendar_events': dict(event_map),
|
||
'month_label': month_start.strftime('%B %Y'),
|
||
'month_value': month_start.strftime('%Y-%m'),
|
||
'prev_month': _previous_month(month_start).strftime('%Y-%m'),
|
||
'next_month': next_month.strftime('%Y-%m'),
|
||
'today_value': today.strftime('%Y-%m'),
|
||
}
|
||
|
||
|
||
def _base_context(**extra):
|
||
context = {
|
||
'project_name': PROJECT_NAME,
|
||
'meta_description': DEFAULT_META_DESCRIPTION,
|
||
}
|
||
context.update(extra)
|
||
return context
|
||
|
||
|
||
def _embed_base_url(request):
|
||
return request.build_absolute_uri(reverse('calendar_embed'))
|
||
|
||
|
||
def _show_embed_header(request):
|
||
return request.GET.get('header', '1') != '0'
|
||
|
||
|
||
@login_required(login_url='login')
|
||
def event_dashboard(request):
|
||
if not request.user.is_staff:
|
||
raise PermissionDenied
|
||
events = Event.objects.all().order_by('start', 'name')
|
||
return render(
|
||
request,
|
||
'core/event_dashboard.html',
|
||
_base_context(
|
||
page_title='Manage events',
|
||
events=events,
|
||
dashboard_count=events.count(),
|
||
embed_base_url=_embed_base_url(request),
|
||
default_embed_month=timezone.localdate().replace(day=1).strftime('%Y-%m'),
|
||
),
|
||
)
|
||
|
||
|
||
@login_required(login_url='login')
|
||
def event_create(request):
|
||
if not request.user.is_staff:
|
||
raise PermissionDenied
|
||
|
||
if request.method == 'POST':
|
||
form = EventForm(request.POST)
|
||
if form.is_valid():
|
||
event = form.save()
|
||
messages.success(request, 'Event saved and ready for the public calendar.')
|
||
return redirect('event_dashboard_detail', slug=event.slug)
|
||
else:
|
||
form = EventForm()
|
||
|
||
return render(
|
||
request,
|
||
'core/event_form.html',
|
||
_base_context(
|
||
page_title='Add event',
|
||
form=form,
|
||
form_mode='create',
|
||
form_title='Add a new stop to the calendar',
|
||
form_intro='Once saved, published events immediately power the landing page, public calendar, and embeddable widget.',
|
||
submit_label='Save event',
|
||
),
|
||
)
|
||
|
||
|
||
@login_required(login_url='login')
|
||
def event_edit(request, slug):
|
||
if not request.user.is_staff:
|
||
raise PermissionDenied
|
||
|
||
event = get_object_or_404(Event, slug=slug)
|
||
if request.method == 'POST':
|
||
form = EventForm(request.POST, instance=event)
|
||
if form.is_valid():
|
||
event = form.save()
|
||
messages.success(request, 'Event updated successfully.')
|
||
return redirect('event_dashboard_detail', slug=event.slug)
|
||
else:
|
||
form = EventForm(instance=event)
|
||
|
||
return render(
|
||
request,
|
||
'core/event_form.html',
|
||
_base_context(
|
||
page_title=f'Edit {event.name}',
|
||
form=form,
|
||
event=event,
|
||
form_mode='edit',
|
||
form_title='Update this calendar stop',
|
||
form_intro='Change dates, copy, publication status, or the event link without leaving the custom dashboard.',
|
||
submit_label='Save changes',
|
||
),
|
||
)
|
||
|
||
|
||
@login_required(login_url='login')
|
||
def event_delete(request, slug):
|
||
if not request.user.is_staff:
|
||
raise PermissionDenied
|
||
|
||
event = get_object_or_404(Event, slug=slug)
|
||
if request.method == 'POST':
|
||
event_name = event.name
|
||
event.delete()
|
||
messages.success(request, f'{event_name} was deleted.')
|
||
return redirect('event_dashboard')
|
||
|
||
return render(
|
||
request,
|
||
'core/event_confirm_delete.html',
|
||
_base_context(
|
||
page_title=f'Delete {event.name}',
|
||
event=event,
|
||
),
|
||
)
|
||
|
||
|
||
@login_required(login_url='login')
|
||
def event_dashboard_detail(request, slug):
|
||
if not request.user.is_staff:
|
||
raise PermissionDenied
|
||
event = get_object_or_404(Event, slug=slug)
|
||
return render(
|
||
request,
|
||
'core/event_dashboard_detail.html',
|
||
_base_context(
|
||
page_title=event.name,
|
||
event=event,
|
||
),
|
||
)
|
||
|
||
|
||
def home(request):
|
||
month_context = _build_month_context(request.GET.get('month'))
|
||
upcoming_events = list(Event.objects.filter(is_published=True, end__gte=timezone.now()).order_by('start', 'name')[:6])
|
||
embed_url = _embed_base_url(request)
|
||
iframe_snippet = f'<iframe src="{embed_url}" title="Where to find us calendar" width="100%" height="760" style="border:0;border-radius:24px;overflow:hidden;"></iframe>'
|
||
return render(
|
||
request,
|
||
'core/index.html',
|
||
_base_context(
|
||
page_title=PROJECT_NAME,
|
||
hero_events=upcoming_events[:3],
|
||
upcoming_events=upcoming_events,
|
||
embed_url=embed_url,
|
||
iframe_snippet=iframe_snippet,
|
||
**month_context,
|
||
),
|
||
)
|
||
|
||
|
||
def calendar_page(request):
|
||
month_context = _build_month_context(request.GET.get('month'))
|
||
return render(
|
||
request,
|
||
'core/calendar_page.html',
|
||
_base_context(
|
||
page_title='Public calendar',
|
||
**month_context,
|
||
),
|
||
)
|
||
|
||
|
||
def calendar_embed(request):
|
||
month_context = _build_month_context(request.GET.get('month'))
|
||
return render(
|
||
request,
|
||
'core/calendar_embed.html',
|
||
_base_context(
|
||
page_title='Embeddable calendar',
|
||
embedded=True,
|
||
show_embed_header=_show_embed_header(request),
|
||
**month_context,
|
||
),
|
||
)
|
||
|
||
|
||
def event_list(request):
|
||
events = Event.objects.filter(is_published=True).order_by('start', 'name')
|
||
return render(
|
||
request,
|
||
'core/event_list.html',
|
||
_base_context(
|
||
page_title='Upcoming events',
|
||
events=events,
|
||
),
|
||
)
|
||
|
||
|
||
def event_detail(request, slug):
|
||
event = get_object_or_404(Event, slug=slug, is_published=True)
|
||
return render(
|
||
request,
|
||
'core/event_detail.html',
|
||
_base_context(
|
||
page_title=event.name,
|
||
event=event,
|
||
),
|
||
)
|