Autosave: 20260206-230153
This commit is contained in:
parent
0d11fc7d5d
commit
935ecf1b68
Binary file not shown.
Binary file not shown.
@ -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'})
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user