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 import forms
|
||||||
from django.contrib.auth.models import User
|
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 .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):
|
class Select2MultipleWidget(forms.SelectMultiple):
|
||||||
"""
|
"""
|
||||||
@ -83,7 +84,6 @@ class VoterForm(forms.ModelForm):
|
|||||||
if self.user.is_superuser:
|
if self.user.is_superuser:
|
||||||
is_admin = True
|
is_admin = True
|
||||||
elif self.tenant:
|
elif self.tenant:
|
||||||
from .permissions import get_user_role
|
|
||||||
role = get_user_role(self.user, self.tenant)
|
role = get_user_role(self.user, self.tenant)
|
||||||
if role in ["admin", "system_admin", "campaign_admin"]:
|
if role in ["admin", "system_admin", "campaign_admin"]:
|
||||||
is_admin = True
|
is_admin = True
|
||||||
@ -137,6 +137,8 @@ class AdvancedVoterSearchForm(forms.Form):
|
|||||||
choices=[('', 'Any')] + Voter.WINDOW_STICKER_CHOICES,
|
choices=[('', 'Any')] + Voter.WINDOW_STICKER_CHOICES,
|
||||||
required=False
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -488,5 +490,4 @@ class UserUpdateForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
for field in self.fields.values():
|
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>
|
<label class="form-label small fw-bold text-muted">Window Sticker</label>
|
||||||
{{ form.window_sticker }}
|
{{ form.window_sticker }}
|
||||||
</div>
|
</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="col-md-4 d-flex align-items-end">
|
||||||
<div class="form-check mb-2">
|
<div class="form-check mb-2">
|
||||||
{{ form.is_targeted }}
|
{{ form.is_targeted }}
|
||||||
@ -324,7 +332,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Individual Schedule Call Modal -->
|
<!-- 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-dialog">
|
||||||
<div class="modal-content border-0 shadow">
|
<div class="modal-content border-0 shadow">
|
||||||
<div class="modal-header border-0 bg-light">
|
<div class="modal-header border-0 bg-light">
|
||||||
|
|||||||
@ -12,17 +12,18 @@ import json
|
|||||||
from django.http import JsonResponse, HttpResponse
|
from django.http import JsonResponse, HttpResponse
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
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.contrib import messages
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.conf import settings
|
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 .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
|
from .forms import VoterForm, InteractionForm, DonationForm, VoterLikelihoodForm, EventParticipationForm, VoterImportForm, AdvancedVoterSearchForm, EventParticipantAddForm, EventForm, VolunteerForm, VolunteerEventForm, VolunteerEventAddForm, DoorVisitLogForm, ScheduledCallForm, UserUpdateForm, EventParticipationImportForm, ParticipantMappingForm
|
||||||
import logging
|
import logging
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
from django.utils import timezone
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def _handle_uploaded_file(uploaded_file):
|
def _handle_uploaded_file(uploaded_file):
|
||||||
@ -397,7 +398,7 @@ def add_event_participation(request, voter_id):
|
|||||||
def edit_event_participation(request, participation_id):
|
def edit_event_participation(request, participation_id):
|
||||||
selected_tenant_id = request.session.get('tenant_id')
|
selected_tenant_id = request.session.get('tenant_id')
|
||||||
tenant = get_object_or_404(Tenant, id=selected_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':
|
if request.method == 'POST':
|
||||||
form = EventParticipationForm(request.POST, instance=participation, tenant=tenant)
|
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):
|
def delete_event_participation(request, participation_id):
|
||||||
selected_tenant_id = request.session.get('tenant_id')
|
selected_tenant_id = request.session.get('tenant_id')
|
||||||
tenant = get_object_or_404(Tenant, id=selected_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
|
voter_id = participation.voter.id
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
@ -510,6 +511,19 @@ def voter_advanced_search(request):
|
|||||||
if data.get('window_sticker'):
|
if data.get('window_sticker'):
|
||||||
voters = voters.filter(window_sticker=data['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)
|
paginator = Paginator(voters, 50)
|
||||||
page_number = request.GET.get('page')
|
page_number = request.GET.get('page')
|
||||||
voters_page = paginator.get_page(page_number)
|
voters_page = paginator.get_page(page_number)
|
||||||
@ -588,6 +602,18 @@ def export_voters_csv(request):
|
|||||||
if data.get('window_sticker'):
|
if data.get('window_sticker'):
|
||||||
voters = voters.filter(window_sticker=data['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')
|
voters = voters.order_by('last_name', 'first_name')
|
||||||
|
|
||||||
response = HttpResponse(content_type='text/csv')
|
response = HttpResponse(content_type='text/csv')
|
||||||
@ -714,6 +740,7 @@ def bulk_send_sms(request):
|
|||||||
# Log interaction
|
# Log interaction
|
||||||
Interaction.objects.create(
|
Interaction.objects.create(
|
||||||
voter=voter,
|
voter=voter,
|
||||||
|
# volunteer=volunteer, # volunteer is not defined here
|
||||||
type=interaction_type,
|
type=interaction_type,
|
||||||
# date=interaction_date, # interaction_date removed
|
# date=interaction_date, # interaction_date removed
|
||||||
description='Mass SMS Text',
|
description='Mass SMS Text',
|
||||||
@ -977,7 +1004,7 @@ def volunteer_delete(request, volunteer_id):
|
|||||||
volunteer.delete()
|
volunteer.delete()
|
||||||
messages.success(request, "Volunteer deleted.")
|
messages.success(request, "Volunteer deleted.")
|
||||||
return redirect('volunteer_list')
|
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')
|
@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.change_volunteer')
|
||||||
def volunteer_assign_event(request, volunteer_id):
|
def volunteer_assign_event(request, volunteer_id):
|
||||||
@ -1739,7 +1766,7 @@ def log_door_visit(request):
|
|||||||
volunteer=volunteer,
|
volunteer=volunteer,
|
||||||
type=interaction_type,
|
type=interaction_type,
|
||||||
date=interaction_date,
|
date=interaction_date,
|
||||||
description="Outcome",
|
description=outcome,
|
||||||
notes=notes
|
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')
|
@role_required(['admin', 'campaign_manager', 'campaign_staff', 'system_admin', 'campaign_admin'], permission='core.delete_scheduledcall')
|
||||||
def delete_call(request, call_id):
|
def delete_call(request, call_id):
|
||||||
selected_tenant_id = request.session.get("tenant_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)
|
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
|
||||||
call = get_object_or_404(ScheduledCall, id=call_id, tenant=tenant)
|
call = get_object_or_404(ScheduledCall, id=call_id, tenant=tenant)
|
||||||
|
|
||||||
@ -1997,5 +2028,3 @@ def profile(request):
|
|||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
u_form = UserUpdateForm(request.POST, instance=request.user)
|
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