Autosave: 20260206-230153

This commit is contained in:
Flatlogic Bot 2026-02-06 23:01:54 +00:00
parent 0d11fc7d5d
commit 935ecf1b68
5 changed files with 50 additions and 12 deletions

View File

@ -1,6 +1,7 @@
from django import forms
from django.contrib.auth.models import User
from .models import Voter, Interaction, Donation, VoterLikelihood, InteractionType, DonationMethod, ElectionType, Event, EventParticipation, EventType, Tenant, ParticipationStatus, Volunteer, VolunteerEvent, VolunteerRole, ScheduledCall
from core.permissions import get_user_role
class Select2MultipleWidget(forms.SelectMultiple):
"""
@ -83,7 +84,6 @@ class VoterForm(forms.ModelForm):
if self.user.is_superuser:
is_admin = True
elif self.tenant:
from .permissions import get_user_role
role = get_user_role(self.user, self.tenant)
if role in ["admin", "system_admin", "campaign_admin"]:
is_admin = True
@ -137,6 +137,8 @@ class AdvancedVoterSearchForm(forms.Form):
choices=[('', 'Any')] + Voter.WINDOW_STICKER_CHOICES,
required=False
)
min_total_donation = forms.DecimalField(required=False, min_value=0, label="Min Total Donation")
max_total_donation = forms.DecimalField(required=False, min_value=0, label="Max Total Donation")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -488,5 +490,4 @@ class UserUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs.update({'class': 'form-control'}
)
field.widget.attrs.update({'class': 'form-control'})

View File

@ -69,6 +69,14 @@
<label class="form-label small fw-bold text-muted">Window Sticker</label>
{{ form.window_sticker }}
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">Min Total Donation</label>
{{ form.min_total_donation }}
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted">Max Total Donation</label>
{{ form.max_total_donation }}
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check mb-2">
{{ form.is_targeted }}
@ -324,7 +332,7 @@
</div>
<!-- Individual Schedule Call Modal -->
<div class="modal fade" id="scheduleCallModal" tabindex="-1" aria-hidden="true">
<div class="modal fade" id="scheduleCallModal" tabindex="-1" aria-labelledby="scheduleCallModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header border-0 bg-light">

View File

@ -12,17 +12,18 @@ import json
from django.http import JsonResponse, HttpResponse
from django.urls import reverse
from django.shortcuts import render, redirect, get_object_or_404
from django.db.models import Q, Sum
from django.db.models import Q, Sum, Value
from django.contrib import messages
from django.core.paginator import Paginator
from django.conf import settings
from django.db.models.functions import Coalesce
from .models import Voter, Tenant, Interaction, Donation, VoterLikelihood, EventParticipation, Event, EventType, InteractionType, DonationMethod, ElectionType, CampaignSettings, Volunteer, ParticipationStatus, VolunteerEvent, Interest, VolunteerRole, ScheduledCall
from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm, VoterImportForm, AdvancedVoterSearchForm, EventParticipantAddForm, EventForm, VolunteerForm, VolunteerEventForm, VolunteerEventAddForm, DoorVisitLogForm, ScheduledCallForm, UserUpdateForm, EventParticipationImportForm, ParticipantMappingForm
import logging
import zoneinfo
from django.utils import timezone
from .permissions import role_required, can_view_donations, can_edit_voter, can_view_volunteers, can_edit_volunteer, can_view_voters
from .permissions import role_required, can_view_donations, can_edit_voter, can_view_volunteers, can_edit_volunteer, can_view_voters, get_user_role
logger = logging.getLogger(__name__)
def _handle_uploaded_file(uploaded_file):
@ -397,7 +398,7 @@ def add_event_participation(request, 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)
participation = get_object_or_404(EventParticipation, id=participation_id, event__tenant=tenant)
if request.method == 'POST':
form = EventParticipationForm(request.POST, instance=participation, tenant=tenant)
@ -413,7 +414,7 @@ def edit_event_participation(request, participation_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)
participation = get_object_or_404(EventParticipation, id=participation_id, event__tenant=tenant)
voter_id = participation.voter.id
if request.method == 'POST':
@ -510,6 +511,19 @@ def voter_advanced_search(request):
if data.get('window_sticker'):
voters = voters.filter(window_sticker=data['window_sticker'])
# Add donation amount filters
min_total_donation = data.get('min_total_donation')
max_total_donation = data.get('max_total_donation')
if min_total_donation is not None or max_total_donation is not None:
# Annotate each voter with their total donation amount, treating no donations as 0
voters = voters.annotate(total_donation_amount=Coalesce(Sum('donations__amount'), Value(0)))
if min_total_donation is not None:
voters = voters.filter(total_donation_amount__gte=min_total_donation)
if max_total_donation is not None:
voters = voters.filter(total_donation_amount__lte=max_total_donation)
paginator = Paginator(voters, 50)
page_number = request.GET.get('page')
voters_page = paginator.get_page(page_number)
@ -588,6 +602,18 @@ def export_voters_csv(request):
if data.get('window_sticker'):
voters = voters.filter(window_sticker=data['window_sticker'])
# Add donation amount filters for export
min_total_donation = data.get('min_total_donation')
max_total_donation = data.get('max_total_donation')
if min_total_donation is not None or max_total_donation is not None:
voters = voters.annotate(total_donation_amount=Coalesce(Sum('donations__amount'), Value(0)))
if min_total_donation is not None:
voters = voters.filter(total_donation_amount__gte=min_total_donation)
if max_total_donation is not None:
voters = voters.filter(total_donation_amount__lte=max_total_donation)
voters = voters.order_by('last_name', 'first_name')
response = HttpResponse(content_type='text/csv')
@ -714,6 +740,7 @@ def bulk_send_sms(request):
# Log interaction
Interaction.objects.create(
voter=voter,
# volunteer=volunteer, # volunteer is not defined here
type=interaction_type,
# date=interaction_date, # interaction_date removed
description='Mass SMS Text',
@ -977,7 +1004,7 @@ def volunteer_delete(request, volunteer_id):
volunteer.delete()
messages.success(request, "Volunteer deleted.")
return redirect('volunteer_list')
return redirect('volunteer_detail', volunteer_id=volunteer_id)
return redirect('volunteer_detail', volunteer_id=volunteer.id)
@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.change_volunteer')
def volunteer_assign_event(request, volunteer_id):
@ -1739,7 +1766,7 @@ def log_door_visit(request):
volunteer=volunteer,
type=interaction_type,
date=interaction_date,
description="Outcome",
description=outcome,
notes=notes
)
@ -1979,6 +2006,10 @@ def complete_call(request, call_id):
@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.delete_scheduledcall')
def delete_call(request, call_id):
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)
call = get_object_or_404(ScheduledCall, id=call_id, tenant=tenant)
@ -1997,5 +2028,3 @@ def profile(request):
if request.method == 'POST':
u_form = UserUpdateForm(request.POST, instance=request.user)
# v_form = VolunteerProfileForm(request.POST, instance=volunteer) if volunteer else None # Removed VolunteerProfileForm
v_form = None # Set v_form to None after removal