252 lines
11 KiB
Python
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
|