37769-vm/core/views.py
2026-02-07 23:39:19 +00:00

616 lines
21 KiB
Python

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.db.models import Count, Case, When, IntegerField, Sum, F, DecimalField
from django.db.models.functions import Coalesce
from django.http import JsonResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponse
from django.core.paginator import Paginator
from django.forms import modelformset_factory
from .models import Tenant, Voter, Interaction, Event, EventParticipation, Volunteer, VolunteerEvent, CampaignSettings, ParticipationStatus, VoterLikelihood, ScheduledCall, VolunteerRole, EventType, TenantUserRole
from .forms import VoterForm, EventForm, VolunteerForm, ScheduledCallForm, InteractionForm
from datetime import datetime, date, time, timedelta
from django.utils import timezone
import pytz
import re
from django.conf import settings
from django.template.defaultfilters import date as date_filter
import csv
# Import necessary modules for Twilio
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
import logging
logger = logging.getLogger(__name__)
# Helper function to get the current tenant
def get_current_tenant(request):
if request.user.is_authenticated:
tenant_role = TenantUserRole.objects.filter(user=request.user).first()
if tenant_role:
return tenant_role.tenant
return None
def get_tenant_campaign_settings(tenant):
if tenant:
try:
return CampaignSettings.objects.get(tenant=tenant)
except CampaignSettings.DoesNotExist:
pass
return None
@login_required
def dashboard(request):
user_tenants = Tenant.objects.filter(user_roles__user=request.user)
if not user_tenants.exists():
return redirect('admin:index')
selected_tenant_id = request.session.get('selected_tenant_id')
if selected_tenant_id:
selected_tenant = get_object_or_404(Tenant, pk=selected_tenant_id)
if selected_tenant not in user_tenants:
# If the selected tenant is not among the user's tenants, reset session and show selection
del request.session['selected_tenant_id']
return redirect('dashboard')
else:
# If no tenant is selected, and there's only one available, select it automatically
if user_tenants.count() == 1:
selected_tenant = user_tenants.first()
request.session['selected_tenant_id'] = selected_tenant.id
else:
# Otherwise, prompt the user to select a tenant
return render(request, 'core/index.html', {'tenants': user_tenants, 'selected_tenant': None})
campaign_settings = get_tenant_campaign_settings(selected_tenant)
# Total Voters
total_voters = Voter.objects.filter(tenant=selected_tenant).count()
# Total Interactions
total_interactions = Interaction.objects.filter(voter__tenant=selected_tenant).count()
return render(request, 'core/index.html', {
'total_voters': total_voters,
'total_interactions': total_interactions,
'campaign_settings': campaign_settings,
'selected_tenant': selected_tenant,
'tenants': user_tenants, # Pass all tenants for potential switching
})
@login_required
def select_campaign(request, tenant_id):
# Ensure the tenant exists and the user has access to it
tenant = get_object_or_404(Tenant, pk=tenant_id, user_roles__user=request.user)
request.session['selected_tenant_id'] = tenant.id
return redirect('dashboard')
# Placeholder views for other functions
@login_required
def voter_list(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voters = Voter.objects.filter(tenant=tenant)
return render(request, 'core/voter_list.html', {'voters': voters})
@login_required
def voter_detail(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voter = get_object_or_404(Voter, pk=pk, tenant=tenant)
return render(request, 'core/voter_detail.html', {'voter': voter})
@login_required
def voter_create(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
if request.method == 'POST':
form = VoterForm(request.POST)
if form.is_valid():
voter = form.save(commit=False)
voter.tenant = tenant
voter.save()
return redirect('voter_detail', pk=voter.pk)
else:
form = VoterForm()
return render(request, 'core/voter_form.html', {'form': form})
@login_required
def voter_update(request, voter_id):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voter = get_object_or_404(Voter, pk=voter_id, tenant=tenant)
if request.method == 'POST':
form = VoterForm(request.POST, instance=voter)
if form.is_valid():
form.save()
return redirect('voter_detail', pk=voter.pk)
else:
form = VoterForm(instance=voter)
return render(request, 'core/voter_form.html', {'form': form, 'voter': voter})
@login_required
def voter_delete(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voter = get_object_or_404(Voter, pk=pk, tenant=tenant)
if request.method == 'POST':
voter.delete()
return redirect('voter_list')
return render(request, 'core/voter_confirm_delete.html', {'voter': voter})
@login_required
def interaction_list(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
interactions = Interaction.objects.filter(voter__tenant=tenant)
return render(request, 'core/interaction_list.html', {'interactions': interactions})
@login_required
def interaction_detail(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
interaction = get_object_or_404(Interaction, pk=pk, voter__tenant=tenant)
return render(request, 'core/interaction_detail.html', {'interaction': interaction})
@login_required
def interaction_create(request, voter_id):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voter = get_object_or_404(Voter, pk=voter_id, tenant=tenant)
if request.method == 'POST':
form = InteractionForm(request.POST)
if form.is_valid():
interaction = form.save(commit=False)
interaction.voter = voter
# Assign volunteer if relevant or leave null
interaction.save()
return redirect('voter_detail', pk=voter.pk)
else:
form = InteractionForm()
return render(request, 'core/interaction_form.html', {'form': form, 'voter': voter})
@login_required
def interaction_update(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
interaction = get_object_or_404(Interaction, pk=pk, voter__tenant=tenant)
if request.method == 'POST':
form = InteractionForm(request.POST, instance=interaction)
if form.is_valid():
form.save()
return redirect('voter_detail', pk=interaction.voter.pk)
else:
form = InteractionForm(instance=interaction)
return render(request, 'core/interaction_form.html', {'form': form, 'interaction': interaction})
@login_required
def interaction_delete(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
interaction = get_object_or_404(Interaction, pk=pk, voter__tenant=tenant)
if request.method == 'POST':
interaction.delete()
return redirect('voter_detail', pk=interaction.voter.pk)
return render(request, 'core/interaction_confirm_delete.html', {'interaction': interaction})
@login_required
def donation_list(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
donations = Interaction.objects.filter(voter__tenant=tenant)
return render(request, 'core/donation_list.html', {'donations': donations})
@login_required
def donation_detail(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
donation = get_object_or_404(Interaction, pk=pk, voter__tenant=tenant)
return render(request, 'core/donation_detail.html', {'donation': donation})
@login_required
def donation_create(request, voter_pk):
return HttpResponse("Placeholder for donation_create")
@login_required
def donation_update(request, voter_pk, pk):
return HttpResponse("Placeholder for donation_update")
@login_required
def donation_delete(request, voter_pk, pk):
return HttpResponse("Placeholder for donation_delete")
@login_required
def likelihood_create(request, voter_pk):
return HttpResponse("Placeholder for likelihood_create")
@login_required
def likelihood_update(request, voter_pk, pk):
return HttpResponse("Placeholder for likelihood_update")
@login_required
def likelihood_delete(request, voter_pk, pk):
return HttpResponse("Placeholder for likelihood_delete")
@login_required
def event_list(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
events = Event.objects.filter(tenant=tenant)
return render(request, 'core/event_list.html', {'events': events})
@login_required
def event_detail(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
event = get_object_or_404(Event, pk=pk, tenant=tenant)
return render(request, 'core/event_detail.html', {'event': event})
@login_required
def event_create(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
if request.method == 'POST':
form = EventForm(request.POST)
if form.is_valid():
event = form.save(commit=False)
event.tenant = tenant
event.save()
return redirect('event_detail', pk=event.pk)
else:
form = EventForm()
return render(request, 'core/event_form.html', {'form': form})
@login_required
def event_update(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
event = get_object_or_404(Event, pk=pk, tenant=tenant)
if request.method == 'POST':
form = EventForm(request.POST, instance=event)
if form.is_valid():
form.save()
return redirect('event_detail', pk=event.pk)
else:
form = EventForm(instance=event)
return render(request, 'core/event_form.html', {'form': form, 'event': event})
@login_required
def event_delete(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
event = get_object_or_404(Event, pk=pk, tenant=tenant)
if request.method == 'POST':
event.delete()
return redirect('event_list')
return render(request, 'core/event_confirm_delete.html', {'event': event})
@login_required
def event_add_participant(request, pk):
return HttpResponse("Placeholder for event_add_participant")
@login_required
def event_edit_participant(request, event_pk, pk):
return HttpResponse("Placeholder for event_edit_participant")
@login_required
def event_delete_participant(request, event_pk, pk):
return HttpResponse("Placeholder for event_delete_participant")
@login_required
def event_participation_create(request, voter_id):
return HttpResponse("Placeholder for event_participation_create")
@login_required
def event_participation_update(request, pk):
return HttpResponse("Placeholder for event_participation_update")
@login_required
def event_participation_delete(request, pk):
return HttpResponse("Placeholder for event_participation_delete")
@login_required
def volunteer_list(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
volunteers = Volunteer.objects.filter(tenant=tenant)
return render(request, 'core/volunteer_list.html', {'volunteers': volunteers})
@login_required
def volunteer_detail(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
volunteer = get_object_or_404(Volunteer, pk=pk, tenant=tenant)
return render(request, 'core/volunteer_detail.html', {'volunteer': volunteer})
@login_required
def volunteer_create(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
if request.method == 'POST':
form = VolunteerForm(request.POST)
if form.is_valid():
volunteer = form.save(commit=False)
volunteer.tenant = tenant
volunteer.save()
return redirect('volunteer_detail', pk=volunteer.pk)
else:
form = VolunteerForm()
return render(request, 'core/volunteer_form.html', {'form': form})
@login_required
def volunteer_edit(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
volunteer = get_object_or_404(Volunteer, pk=pk, tenant=tenant)
if request.method == 'POST':
form = VolunteerForm(request.POST, instance=volunteer)
if form.is_valid():
form.save()
return redirect('volunteer_detail', pk=volunteer.pk)
else:
form = VolunteerForm(instance=volunteer)
return render(request, 'core/volunteer_form.html', {'form': form, 'voter': volunteer.pk})
@login_required
def volunteer_delete(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
volunteer = get_object_or_404(Volunteer, pk=pk, tenant=tenant)
if request.method == 'POST':
volunteer.delete()
return redirect('volunteer_list')
return render(request, 'core/volunteer_confirm_delete.html', {'volunteer': volunteer})
@login_required
def voter_geocode(request, voter_pk):
return HttpResponse("Placeholder for voter_geocode")
@login_required
def export_voters_csv(request):
return HttpResponse("Placeholder for export_voters_csv")
@login_required
def create_scheduled_call(request, voter_id):
return HttpResponse("Placeholder for create_scheduled_call")
@login_required
def scheduled_call_list(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
scheduled_calls = ScheduledCall.objects.filter(tenant=tenant)
return render(request, 'core/scheduled_call_list.html', {'scheduled_calls': scheduled_calls})
@login_required
def scheduled_call_detail(request, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
scheduled_call = get_object_or_404(ScheduledCall, pk=pk, tenant=tenant)
return render(request, 'core/scheduled_call_detail.html', {'scheduled_call': scheduled_call})
@login_required
def scheduled_call_create(request, voter_pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voter = get_object_or_404(Voter, pk=voter_pk, tenant=tenant)
if request.method == 'POST':
form = ScheduledCallForm(request.POST)
if form.is_valid():
scheduled_call = form.save(commit=False)
scheduled_call.voter = voter
scheduled_call.tenant = tenant
scheduled_call.save()
return redirect('scheduled_call_detail', pk=scheduled_call.pk)
else:
form = ScheduledCallForm(initial={'voter': voter})
return render(request, 'core/scheduled_call_form.html', {'form': form, 'voter': voter})
@login_required
def scheduled_call_edit(request, voter_pk, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
scheduled_call = get_object_or_404(ScheduledCall, pk=pk, voter__pk=voter_pk, tenant=tenant)
if request.method == 'POST':
form = ScheduledCallForm(request.POST, instance=scheduled_call)
if form.is_valid():
form.save()
return redirect('scheduled_call_detail', pk=scheduled_call.pk)
else:
form = ScheduledCallForm(instance=scheduled_call)
return render(request, 'core/scheduled_call_form.html', {'form': form, 'voter': scheduled_call.voter})
@login_required
def scheduled_call_delete(request, voter_pk, pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
scheduled_call = get_object_or_404(ScheduledCall, pk=pk, voter__pk=voter_pk, tenant=tenant)
if request.method == 'POST':
scheduled_call.delete()
return redirect('scheduled_call_list')
return render(request, 'core/scheduled_call_confirm_delete.html', {'scheduled_call': scheduled_call})
@login_required
def bulk_schedule_calls(request):
return HttpResponse("Placeholder for bulk_schedule_calls")
@login_required
def voter_search_json(request):
return HttpResponse("Placeholder for voter_search_json")
@login_required
def import_participants(request):
return HttpResponse("Placeholder for import_participants")
@login_required
def import_participants_map_fields(request):
return HttpResponse("Placeholder for import_participants_map_fields")
@login_required
def process_participants_import(request):
return HttpResponse("Placeholder for process_participants_import")
@login_required
def match_participants(request):
return HttpResponse("Placeholder for match_participants")
@login_required
def interest_add(request):
return HttpResponse("Placeholder for interest_add")
@login_required
def interest_delete(request):
return HttpResponse("Placeholder for interest_delete")
@login_required
def volunteer_add(request):
return HttpResponse("Placeholder for volunteer_add")
@login_required
def volunteer_assign_event(request, volunteer_pk):
return HttpResponse("Placeholder for volunteer_assign_event")
@login_required
def volunteer_remove_event(request, volunteer_pk, event_pk):
return HttpResponse("Placeholder for volunteer_remove_event")
@login_required
def volunteer_search_json(request):
return HttpResponse("Placeholder for volunteer_search_json")
@login_required
def bulk_send_sms(request):
return HttpResponse("Placeholder for bulk_send_sms")
@login_required
def create_interaction_for_voter(request, voter_pk):
return HttpResponse("Placeholder for create_interaction_for_voter")
@login_required
def complete_call(request, call_pk):
return HttpResponse("Placeholder for complete_call")
@login_required
def door_visits(request):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
# Get voters for the current tenant that have door_visit set to True
door_to_door_voters = Voter.objects.filter(tenant=tenant, door_visit=True)
# Dictionary to store visited households with the latest visit date
visited_households = {}
for voter in door_to_door_voters:
# Construct a unique key for the household (e.g., street address and city)
household_key = f"{voter.address_street.lower().strip()}-{voter.city.lower().strip()}"
# Find the latest interaction for this voter
latest_interaction = Interaction.objects.filter(voter=voter).order_by('-date').first()
# Update visited_households with the latest interaction date for the household
if household_key not in visited_households or (latest_interaction and latest_interaction.date > visited_households[household_key]['last_visit_date']):
visited_households[household_key] = {
'voter': voter,
'last_visit_date': latest_interaction.date if latest_interaction else None,
'voters_in_household': []
}
# Ensure 'last_visit_date' is always present in the dictionary for comparison
if 'last_visit_date' not in visited_households[household_key]:
visited_households[household_key]['last_visit_date'] = None
visited_households[household_key]['voters_in_household'].append(voter)
# Sort households by the last visit date, with None dates appearing last
sorted_households = sorted(visited_households.values(), key=lambda x: x['last_visit_date'] if x['last_visit_date'] is not None else datetime.min.replace(tzinfo=pytz.UTC), reverse=True)
# Render the door_visits.html template with the sorted household data
return render(request, 'core/door_visits.html', {'households': sorted_households})
@login_required
def door_visit_history(request, voter_pk):
tenant = get_current_tenant(request)
if not tenant:
return redirect('admin:index')
voter = get_object_or_404(Voter, pk=voter_pk, tenant=tenant)
interactions = Interaction.objects.filter(voter=voter).order_by('-date')
context = {
'voter': voter,
'interactions': interactions
}
return render(request, 'core/door_visit_history.html', context)
@login_required
def voter_advanced_search(request):
return HttpResponse("Placeholder for voter_advanced_search")
@login_required
def volunteer_event_create(request, event_id):
return HttpResponse("Placeholder for volunteer_event_create")
@login_required
def volunteer_event_delete(request, pk):
return HttpResponse("Placeholder for volunteer_event_delete")
@login_required
def call_queue(request):
return HttpResponse("Placeholder for call_queue")
@login_required
def profile(request):
return HttpResponse("Placeholder for profile")
@login_required
def volunteer_bulk_send_sms(request):
return HttpResponse("Placeholder for volunteer_bulk_send_sms")