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