from django.shortcuts import render, redirect, get_object_or_404 from django.db.models import Q from django.contrib import messages from .models import Voter, Tenant, Interaction, Donation, VoterLikelihood, EventParticipation, Event, EventType, InteractionType, DonationMethod, ElectionType from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm, EventTypeForm def index(request): """ Main landing page for Grassroots Campaign Manager. Displays a list of campaigns if the user is logged in but hasn't selected one. """ tenants = Tenant.objects.all() selected_tenant_id = request.session.get('tenant_id') selected_tenant = None if selected_tenant_id: selected_tenant = Tenant.objects.filter(id=selected_tenant_id).first() context = { 'tenants': tenants, 'selected_tenant': selected_tenant, } return render(request, 'core/index.html', context) def select_campaign(request, tenant_id): """ Sets the selected campaign in the session. """ tenant = get_object_or_404(Tenant, id=tenant_id) request.session['tenant_id'] = tenant.id messages.success(request, f"You are now managing: {tenant.name}") return redirect('index') def voter_list(request): """ List and search voters. Restricted to selected tenant. """ selected_tenant_id = request.session.get('tenant_id') if not selected_tenant_id: messages.warning(request, "Please select a campaign first.") return redirect('index') tenant = get_object_or_404(Tenant, id=selected_tenant_id) query = request.GET.get('q') voters = Voter.objects.filter(tenant=tenant) if query: query = query.strip() search_filter = Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(voter_id__icontains=query) if "," in query: parts = [p.strip() for p in query.split(",")] if len(parts) >= 2: last_part = parts[0] first_part = parts[1] search_filter |= Q(last_name__icontains=last_part, first_name__icontains=first_part) elif " " in query: parts = query.split() if len(parts) >= 2: first_part = parts[0] last_part = " ".join(parts[1:]) search_filter |= Q(first_name__icontains=first_part, last_name__icontains=last_part) voters = voters.filter(search_filter) context = { 'voters': voters, 'query': query, 'selected_tenant': tenant } return render(request, 'core/voter_list.html', context) def voter_detail(request, voter_id): """ 360-degree view of a voter. """ selected_tenant_id = request.session.get('tenant_id') if not selected_tenant_id: messages.warning(request, "Please select a campaign first.") return redirect('index') tenant = get_object_or_404(Tenant, id=selected_tenant_id) voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) context = { 'voter': voter, 'selected_tenant': tenant, 'voting_records': voter.voting_records.all().order_by('-election_date'), 'donations': voter.donations.all().order_by('-date'), 'interactions': voter.interactions.all().order_by('-date'), 'event_participations': voter.event_participations.all().order_by('-event__date'), 'likelihoods': voter.likelihoods.all(), 'voter_form': VoterForm(instance=voter), 'interaction_form': InteractionForm(tenant=tenant), 'donation_form': DonationForm(tenant=tenant), 'likelihood_form': VoterLikelihoodForm(tenant=tenant), 'event_participation_form': EventParticipationForm(tenant=tenant), } return render(request, 'core/voter_detail.html', context) def voter_edit(request, voter_id): """ Update voter core demographics. """ selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) if request.method == 'POST': form = VoterForm(request.POST, instance=voter) if form.is_valid(): form.save() messages.success(request, "Voter profile updated successfully.") return redirect('voter_detail', voter_id=voter.id) return redirect('voter_detail', voter_id=voter.id) def add_interaction(request, voter_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) if request.method == 'POST': form = InteractionForm(request.POST, tenant=tenant) if form.is_valid(): interaction = form.save(commit=False) interaction.voter = voter interaction.save() messages.success(request, "Interaction added.") return redirect('voter_detail', voter_id=voter.id) def edit_interaction(request, interaction_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) interaction = get_object_or_404(Interaction, id=interaction_id, voter__tenant=tenant) if request.method == 'POST': form = InteractionForm(request.POST, instance=interaction, tenant=tenant) if form.is_valid(): form.save() messages.success(request, "Interaction updated.") return redirect('voter_detail', voter_id=interaction.voter.id) def delete_interaction(request, interaction_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) interaction = get_object_or_404(Interaction, id=interaction_id, voter__tenant=tenant) voter_id = interaction.voter.id if request.method == 'POST': interaction.delete() messages.success(request, "Interaction deleted.") return redirect('voter_detail', voter_id=voter_id) def add_donation(request, voter_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) if request.method == 'POST': form = DonationForm(request.POST, tenant=tenant) if form.is_valid(): donation = form.save(commit=False) donation.voter = voter donation.save() messages.success(request, "Donation recorded.") return redirect('voter_detail', voter_id=voter.id) def edit_donation(request, donation_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) donation = get_object_or_404(Donation, id=donation_id, voter__tenant=tenant) if request.method == 'POST': form = DonationForm(request.POST, instance=donation, tenant=tenant) if form.is_valid(): form.save() messages.success(request, "Donation updated.") return redirect('voter_detail', voter_id=donation.voter.id) def delete_donation(request, donation_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) donation = get_object_or_404(Donation, id=donation_id, voter__tenant=tenant) voter_id = donation.voter.id if request.method == 'POST': donation.delete() messages.success(request, "Donation deleted.") return redirect('voter_detail', voter_id=voter_id) def add_likelihood(request, voter_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) if request.method == 'POST': form = VoterLikelihoodForm(request.POST, tenant=tenant) if form.is_valid(): likelihood = form.save(commit=False) likelihood.voter = voter # Handle potential duplicate election_type VoterLikelihood.objects.filter(voter=voter, election_type=likelihood.election_type).delete() likelihood.save() messages.success(request, "Likelihood updated.") return redirect('voter_detail', voter_id=voter.id) def edit_likelihood(request, likelihood_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) likelihood = get_object_or_404(VoterLikelihood, id=likelihood_id, voter__tenant=tenant) if request.method == 'POST': form = VoterLikelihoodForm(request.POST, instance=likelihood, tenant=tenant) if form.is_valid(): # Check for conflict with another record of same election_type election_type = form.cleaned_data['election_type'] if VoterLikelihood.objects.filter(voter=likelihood.voter, election_type=election_type).exclude(id=likelihood.id).exists(): VoterLikelihood.objects.filter(voter=likelihood.voter, election_type=election_type).exclude(id=likelihood.id).delete() form.save() messages.success(request, "Likelihood updated.") return redirect('voter_detail', voter_id=likelihood.voter.id) def delete_likelihood(request, likelihood_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) likelihood = get_object_or_404(VoterLikelihood, id=likelihood_id, voter__tenant=tenant) voter_id = likelihood.voter.id if request.method == 'POST': likelihood.delete() messages.success(request, "Likelihood record deleted.") return redirect('voter_detail', voter_id=voter_id) def add_event_participation(request, voter_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) voter = get_object_or_404(Voter, id=voter_id, tenant=tenant) if request.method == 'POST': form = EventParticipationForm(request.POST, tenant=tenant) if form.is_valid(): participation = form.save(commit=False) participation.voter = voter # Avoid duplicate participation if not EventParticipation.objects.filter(voter=voter, event=participation.event).exists(): participation.save() messages.success(request, "Event participation added.") else: messages.warning(request, "Voter is already participating in this event.") return redirect('voter_detail', voter_id=voter.id) def edit_event_participation(request, participation_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) participation = get_object_or_404(EventParticipation, id=participation_id, voter__tenant=tenant) if request.method == 'POST': form = EventParticipationForm(request.POST, instance=participation, tenant=tenant) if form.is_valid(): event = form.cleaned_data['event'] if EventParticipation.objects.filter(voter=participation.voter, event=event).exclude(id=participation.id).exists(): messages.warning(request, "Voter is already participating in that event.") else: form.save() messages.success(request, "Event participation updated.") return redirect('voter_detail', voter_id=participation.voter.id) def delete_event_participation(request, participation_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) participation = get_object_or_404(EventParticipation, id=participation_id, voter__tenant=tenant) voter_id = participation.voter.id if request.method == 'POST': participation.delete() messages.success(request, "Event participation removed.") return redirect('voter_detail', voter_id=voter_id) def event_type_list(request): """ Maintenance page for Event Types. """ selected_tenant_id = request.session.get('tenant_id') if not selected_tenant_id: messages.warning(request, "Please select a campaign first.") return redirect('index') tenant = get_object_or_404(Tenant, id=selected_tenant_id) event_types = EventType.objects.filter(tenant=tenant) context = { 'event_types': event_types, 'selected_tenant': tenant, 'form': EventTypeForm(), } return render(request, 'core/event_type_list.html', context) def event_type_add(request): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) if request.method == 'POST': form = EventTypeForm(request.POST) if form.is_valid(): event_type = form.save(commit=False) event_type.tenant = tenant event_type.save() messages.success(request, "Event type added.") return redirect('event_type_list') def event_type_edit(request, type_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) event_type = get_object_or_404(EventType, id=type_id, tenant=tenant) if request.method == 'POST': form = EventTypeForm(request.POST, instance=event_type) if form.is_valid(): form.save() messages.success(request, "Event type updated.") return redirect('event_type_list') def event_type_delete(request, type_id): selected_tenant_id = request.session.get('tenant_id') tenant = get_object_or_404(Tenant, id=selected_tenant_id) event_type = get_object_or_404(EventType, id=type_id, tenant=tenant) if request.method == 'POST': try: event_type.delete() messages.success(request, "Event type deleted.") except Exception as e: messages.error(request, f"Cannot delete event type: {e}") return redirect('event_type_list')