def bulk_send_sms(request): """ Sends bulk SMS to selected voters using Twilio API. """ if request.method != 'POST': return redirect('voter_advanced_search') 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) settings = getattr(tenant, 'settings', None) if not settings: messages.error(request, "Campaign settings not found.") return redirect('voter_advanced_search') account_sid = settings.twilio_account_sid auth_token = settings.twilio_auth_token from_number = settings.twilio_from_number if not account_sid or not auth_token or not from_number: messages.error(request, "Twilio configuration is incomplete in Campaign Settings.") return redirect('voter_advanced_search') message_body = request.POST.get('message_body') if not message_body: messages.error(request, "Message body cannot be empty.") return redirect('voter_advanced_search') select_all_results = request.POST.get('select_all_results') == 'true' if select_all_results: voters, _ = get_filtered_voter_queryset(request, tenant, data_source='POST') voters = voters.filter(phone_type='cell').exclude(phone='') else: voter_ids = request.POST.getlist('selected_voters') voters = Voter.objects.filter(tenant=tenant, id__in=voter_ids, phone_type='cell').exclude(phone='') if not voters.exists(): messages.warning(request, "No voters with a valid cell phone number were selected.") return redirect('voter_advanced_search') success_count = 0 fail_count = 0 auth_str = f"{account_sid}:{auth_token}" auth_header = base64.b64encode(auth_str.encode()).decode() url = f"https://api.twilio.com/2010-04-01/Accounts/{account_sid}/Messages.json" interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="SMS Text") for voter in voters: digits = re.sub(r'\D', '', str(voter.phone)) if len(digits) == 10: to_number = f"+1{digits}" elif len(digits) == 11 and digits.startswith('1'): to_number = f"+{digits}" else: fail_count += 1 continue data_dict = { 'To': to_number, 'From': from_number, 'Body': message_body } data = urllib.parse.urlencode(data_dict).encode() req = urllib.request.Request(url, data=data, method='POST') req.add_header("Authorization", f"Basic {auth_header}") try: with urllib.request.urlopen(req, timeout=10) as response: if response.status in [200, 201]: success_count += 1 Interaction.objects.create( voter=voter, type=interaction_type, date=timezone.now(), description='Mass SMS Text', notes=message_body ) else: fail_count += 1 except Exception as e: logger.error(f"Error sending SMS to {voter.phone}: {e}") fail_count += 1 messages.success(request, f"Bulk SMS process completed: {success_count} successful, {fail_count} failed/skipped.") return redirect('voter_advanced_search')