39586-vm/core/views.py
2026-04-12 12:41:59 +00:00

252 lines
11 KiB
Python

import csv
import logging
import os
import platform
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from django.db import transaction
from django.db.models import Count, Q
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from .forms import EventSearchForm, OrganizerEventForm, RegistrationForm
from .models import Event, Registration
logger = logging.getLogger(__name__)
def _base_context(request):
host_name = request.get_host().lower()
agent_brand = 'AppWizzy' if host_name == 'appwizzy.com' else 'Flatlogic'
now = timezone.now()
return {
'project_name': 'Northstar Events',
'agent_brand': agent_brand,
'django_version': __import__('django').get_version(),
'python_version': platform.python_version(),
'current_time': now,
'host_name': host_name,
'project_description': os.getenv('PROJECT_DESCRIPTION', 'Modern event publishing and registration for a single organizer team.'),
'project_image_url': os.getenv('PROJECT_IMAGE_URL', ''),
}
def home(request):
now = timezone.now()
search_form = EventSearchForm(request.GET or None)
events = Event.objects.filter(is_published=True, end_at__gte=now).annotate(
confirmed_registrations=Count('registrations', filter=Q(registrations__status=Registration.Status.CONFIRMED)),
waitlist_registrations=Count('registrations', filter=Q(registrations__status=Registration.Status.WAITLIST)),
)
query = ''
if search_form.is_valid():
query = search_form.cleaned_data.get('q', '').strip()
if query:
events = events.filter(
Q(title__icontains=query)
| Q(summary__icontains=query)
| Q(description__icontains=query)
| Q(venue__icontains=query)
)
event_list = list(events)
featured_event = event_list[0] if event_list else None
stats = {
'upcoming_events': Event.objects.filter(is_published=True, end_at__gte=now).count(),
'confirmed_attendees': Registration.objects.filter(status=Registration.Status.CONFIRMED).count(),
'waitlist_total': Registration.objects.filter(status=Registration.Status.WAITLIST).count(),
}
context = {
**_base_context(request),
'page_title': 'Northstar Events | Browse upcoming events and register online',
'meta_description': 'Discover upcoming workshops, talks, and community events, then register in minutes with instant confirmation.',
'search_form': search_form,
'events': event_list,
'featured_event': featured_event,
'stats': stats,
'query': query,
}
return render(request, 'core/index.html', context)
def event_detail(request, slug):
event = get_object_or_404(
Event.objects.annotate(
confirmed_registrations=Count('registrations', filter=Q(registrations__status=Registration.Status.CONFIRMED)),
waitlist_registrations=Count('registrations', filter=Q(registrations__status=Registration.Status.WAITLIST)),
),
slug=slug,
is_published=True,
)
form = RegistrationForm(request.POST or None, event=event)
if request.method == 'POST':
if not event.registration_is_open:
form.add_error(None, 'Registration is not open for this event right now.')
elif form.is_valid():
with transaction.atomic():
locked_event = Event.objects.select_for_update().get(pk=event.pk)
if Registration.objects.filter(event=locked_event, email__iexact=form.cleaned_data['email']).exists():
form.add_error('email', 'This email is already registered for this event.')
else:
registration = form.save(commit=False)
registration.event = locked_event
if locked_event.capacity and locked_event.confirmed_registrations_count >= locked_event.capacity:
registration.status = Registration.Status.WAITLIST
else:
registration.status = Registration.Status.CONFIRMED
registration.save()
email_sent = _send_registration_email(registration)
if email_sent:
messages.success(request, 'Registration saved and confirmation email sent.')
else:
messages.warning(request, 'Registration saved. Email delivery is not configured yet, so no confirmation email was sent.')
return redirect('registration_success', slug=locked_event.slug, registration_id=registration.pk)
context = {
**_base_context(request),
'page_title': f'{event.title} | Register with Northstar Events',
'meta_description': event.summary,
'event': event,
'form': form,
'related_events': Event.objects.filter(is_published=True, end_at__gte=timezone.now()).exclude(pk=event.pk)[:3],
}
return render(request, 'core/event_detail.html', context)
def registration_success(request, slug, registration_id):
registration = get_object_or_404(
Registration.objects.select_related('event'),
pk=registration_id,
event__slug=slug,
)
context = {
**_base_context(request),
'page_title': f'Registration confirmed | {registration.event.title}',
'meta_description': f'Confirmation details for {registration.event.title}.',
'registration': registration,
'event': registration.event,
}
return render(request, 'core/registration_success.html', context)
@login_required
def organizer_dashboard(request):
now = timezone.now()
events = Event.objects.annotate(
confirmed_registrations=Count('registrations', filter=Q(registrations__status=Registration.Status.CONFIRMED)),
waitlist_registrations=Count('registrations', filter=Q(registrations__status=Registration.Status.WAITLIST)),
total_registrations=Count('registrations'),
).order_by('start_at')
recent_registrations = Registration.objects.select_related('event').order_by('-created_at')[:8]
context = {
**_base_context(request),
'page_title': 'Organizer dashboard | Northstar Events',
'meta_description': 'Review upcoming events, attendee counts, and recent registrations for your organizer team.',
'events': events,
'recent_registrations': recent_registrations,
'dashboard_stats': {
'upcoming_events': events.filter(end_at__gte=now, is_published=True).count(),
'confirmed_attendees': Registration.objects.filter(status=Registration.Status.CONFIRMED).count(),
'waitlist_total': Registration.objects.filter(status=Registration.Status.WAITLIST).count(),
},
}
return render(request, 'core/organizer_dashboard.html', context)
@login_required
def organizer_event_create(request):
form = OrganizerEventForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
event = form.save()
messages.success(request, f'Event "{event.title}" created successfully.')
return redirect('organizer_dashboard')
context = {
**_base_context(request),
'page_title': 'Create event | Northstar Events',
'meta_description': 'Create a new public event from the organizer dashboard.',
'form': form,
'form_mode': 'create',
'form_title': 'Create a new event without opening Django Admin.',
'form_intro': 'This custom organizer form covers the core publishing fields: schedule, venue, capacity, registration window, and public visibility.',
'form_section_title': 'Fill in the event details.',
'submit_label': 'Create event',
}
return render(request, 'core/organizer_event_form.html', context)
@login_required
def organizer_event_edit(request, slug):
event = get_object_or_404(Event, slug=slug)
form = OrganizerEventForm(request.POST or None, instance=event)
if request.method == 'POST' and form.is_valid():
updated_event = form.save()
messages.success(request, f'Event "{updated_event.title}" updated successfully.')
return redirect('organizer_dashboard')
context = {
**_base_context(request),
'page_title': f'Edit {event.title} | Northstar Events',
'meta_description': f'Update the event details for {event.title} from the organizer dashboard.',
'form': form,
'event': event,
'form_mode': 'edit',
'form_title': f'Edit “{event.title}” from the organizer dashboard.',
'form_intro': 'Update the same publishing fields used during event creation, while keeping the public event URL stable.',
'form_section_title': 'Update the event details.',
'submit_label': 'Save changes',
}
return render(request, 'core/organizer_event_form.html', context)
@login_required
def export_attendees_csv(request, slug):
event = get_object_or_404(Event, slug=slug)
registrations = event.registrations.order_by('created_at')
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{event.slug}-attendees.csv"'
writer = csv.writer(response)
writer.writerow(['Full name', 'Email', 'Company', 'Status', 'Registered at'])
for registration in registrations:
writer.writerow([
registration.full_name,
registration.email,
registration.company,
registration.get_status_display(),
timezone.localtime(registration.created_at).strftime('%Y-%m-%d %H:%M'),
])
return response
def _send_registration_email(registration):
subject = f'Your registration for {registration.event.title}'
if registration.status == Registration.Status.WAITLIST:
intro = 'You have been added to the waitlist.'
else:
intro = 'Your spot is confirmed.'
message = (
f'Hi {registration.full_name},\n\n'
f'{intro}\n\n'
f'Event: {registration.event.title}\n'
f'When: {timezone.localtime(registration.event.start_at).strftime("%B %d, %Y at %I:%M %p")}\n'
f'Where: {registration.event.venue}\n\n'
'We look forward to seeing you.\n'
'Northstar Events'
)
try:
send_mail(
subject,
message,
os.getenv('DEFAULT_FROM_EMAIL', 'no-reply@example.com'),
[registration.email],
fail_silently=False,
)
return True
except Exception as exc:
logger.exception('Registration email failed for %s: %s', registration.email, exc)
return False