Compare commits
2 Commits
d3537b6427
...
c92857d73b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c92857d73b | ||
|
|
01c62eb11d |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,11 +1,73 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Profile{% endblock %}
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h1>User Profile</h1>
|
||||
<p>This is a placeholder for the user profile page.</p>
|
||||
<p>Selected Tenant: {{ selected_tenant.name }}</p>
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm rounded-4 overflow-hidden mb-4">
|
||||
<div class="card-header bg-white border-0 py-4 px-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-primary bg-opacity-10 p-3 rounded-4 me-3">
|
||||
<i class="bi bi-person-circle text-primary fs-3"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="h4 fw-bold mb-0">My Profile</h2>
|
||||
<p class="text-muted small mb-0">Manage your account information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<h5 class="fw-bold mb-3">User Information</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-muted">First Name</label>
|
||||
{{ u_form.first_name }}
|
||||
{% if u_form.first_name.errors %}
|
||||
<div class="text-danger small">{{ u_form.first_name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-muted">Last Name</label>
|
||||
{{ u_form.last_name }}
|
||||
{% if u_form.last_name.errors %}
|
||||
<div class="text-danger small">{{ u_form.last_name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold text-muted">Email Address</label>
|
||||
{{ u_form.email }}
|
||||
{% if u_form.email.errors %}
|
||||
<div class="text-danger small">{{ u_form.email.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if v_form %}
|
||||
<hr class="my-4 opacity-10">
|
||||
<h5 class="fw-bold mb-3">Volunteer Details</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label small fw-bold text-muted">Phone Number</label>
|
||||
{{ v_form.phone }}
|
||||
{% if v_form.phone.errors %}
|
||||
<div class="text-danger small">{{ v_form.phone.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary px-4 rounded-3">Save Changes</button>
|
||||
<a href="{% url 'password_change' %}" class="btn btn-outline-secondary px-4 rounded-3">Change Password</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
52
core/urls.py
52
core/urls.py
@ -1,43 +1,41 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.dashboard, name='dashboard'),
|
||||
path('', views.index, name='index'),
|
||||
path('select-campaign/<int:tenant_id>/', views.select_campaign, name='select_campaign'),
|
||||
path('voters/', views.voter_list, name='voter_list'),
|
||||
path('voters/advanced-search/', views.voter_advanced_search, name='voter_advanced_search'),
|
||||
path('voters/export-csv/', views.export_voters_csv, name='export_voters_csv'),
|
||||
path('voters/bulk-sms/', views.bulk_send_sms, name='bulk_send_sms'),
|
||||
path('voters/<int:voter_id>/', views.voter_detail, name='voter_detail'),
|
||||
path('voters/<int:voter_id>/edit/', views.voter_update, name='voter_edit'), # Changed to voter_update
|
||||
path('voters/<int:voter_id>/edit/', views.voter_edit, name='voter_edit'),
|
||||
path('voters/<int:voter_id>/delete/', views.voter_delete, name='voter_delete'),
|
||||
path('voters/<int:voter_id>/geocode/', views.voter_geocode, name='voter_geocode'),
|
||||
path('voters/<int:voter_id>/schedule-call/', views.create_scheduled_call, name='schedule_call'), # Changed to create_scheduled_call
|
||||
path('voters/<int:voter_id>/schedule-call/', views.schedule_call, name='schedule_call'),
|
||||
path('voters/bulk-schedule-calls/', views.bulk_schedule_calls, name='bulk_schedule_calls'),
|
||||
|
||||
path('voters/<int:voter_id>/interaction/add/', views.interaction_create, name='add_interaction'), # Changed to interaction_create
|
||||
path('interaction/<int:pk>/edit/', views.interaction_update, name='edit_interaction'), # Changed to pk and interaction_update
|
||||
path('interaction/<int:pk>/delete/', views.interaction_delete, name='delete_interaction'), # Changed to pk and interaction_delete
|
||||
path('voters/<int:voter_id>/interaction/add/', views.add_interaction, name='add_interaction'),
|
||||
path('interaction/<int:interaction_id>/edit/', views.edit_interaction, name='edit_interaction'),
|
||||
path('interaction/<int:interaction_id>/delete/', views.delete_interaction, name='delete_interaction'),
|
||||
|
||||
# Assuming donation, likelihood, event-participation views are correctly named in views.py and use 'pk'
|
||||
path('voters/<int:voter_id>/donation/add/', views.donation_create, name='add_donation'),
|
||||
path('donation/<int:pk>/edit/', views.donation_update, name='edit_donation'),
|
||||
path('donation/<int:pk>/delete/', views.donation_delete, name='delete_donation'),
|
||||
path('voters/<int:voter_id>/donation/add/', views.add_donation, name='add_donation'),
|
||||
path('donation/<int:donation_id>/edit/', views.edit_donation, name='edit_donation'),
|
||||
path('donation/<int:donation_id>/delete/', views.delete_donation, name='delete_donation'),
|
||||
|
||||
path('voters/<int:voter_id>/likelihood/add/', views.likelihood_create, name='add_likelihood'),
|
||||
path('likelihood/<int:pk>/edit/', views.likelihood_update, name='edit_likelihood'),
|
||||
path('likelihood/<int:pk>/delete/', views.likelihood_delete, name='delete_likelihood'),
|
||||
path('voters/<int:voter_id>/likelihood/add/', views.add_likelihood, name='add_likelihood'),
|
||||
path('likelihood/<int:likelihood_id>/edit/', views.edit_likelihood, name='edit_likelihood'),
|
||||
path('likelihood/<int:likelihood_id>/delete/', views.delete_likelihood, name='delete_likelihood'),
|
||||
|
||||
path('voters/<int:voter_id>/event-participation/add/', views.event_participation_create, name='add_event_participation'),
|
||||
path('event-participation/<int:pk>/edit/', views.event_participation_update, name='edit_event_participation'),
|
||||
path('event-participation/<int:pk>/delete/', views.event_participation_delete, name='delete_event_participation'),
|
||||
path('voters/<int:voter_id>/event-participation/add/', views.add_event_participation, name='add_event_participation'),
|
||||
path('event-participation/<int:participation_id>/edit/', views.edit_event_participation, name='edit_event_participation'),
|
||||
path('event-participation/<int:participation_id>/delete/', views.delete_event_participation, name='delete_event_participation'),
|
||||
|
||||
# Event Detail and Participant Management
|
||||
path('events/', views.event_list, name='event_list'),
|
||||
path('events/<int:pk>/', views.event_detail, name='event_detail'), # Changed to pk
|
||||
path('events/<int:event_id>/', views.event_detail, name='event_detail'),
|
||||
path('events/add/', views.event_create, name='event_create'),
|
||||
path('events/<int:pk>/edit/', views.event_update, name='event_edit'), # Changed to pk and event_update
|
||||
path('events/<int:event_id>/edit/', views.event_edit, name='event_edit'),
|
||||
path('events/<int:event_id>/participant/add/', views.event_add_participant, name='event_add_participant'),
|
||||
path('events/participant/<int:participation_id>/edit/', views.event_edit_participant, name='event_edit_participant'),
|
||||
path('events/participant/<int:participation_id>/delete/', views.event_delete_participant, name='event_delete_participant'),
|
||||
@ -51,24 +49,24 @@ urlpatterns = [
|
||||
path('interests/add/', views.interest_add, name='interest_add'),
|
||||
path('interests/<int:interest_id>/delete/', views.interest_delete, name='interest_delete'),
|
||||
path('volunteers/', views.volunteer_list, name='volunteer_list'),
|
||||
path('volunteers/add/', views.volunteer_create, name='volunteer_add'), # Changed to volunteer_create
|
||||
path('volunteers/<int:pk>/', views.volunteer_detail, name='volunteer_detail'), # Changed to pk
|
||||
path('volunteers/<int:pk>/delete/', views.volunteer_delete, name='volunteer_delete'), # Changed to pk
|
||||
path('volunteers/add/', views.volunteer_add, name='volunteer_add'),
|
||||
path('volunteers/<int:volunteer_id>/', views.volunteer_detail, name='volunteer_detail'),
|
||||
path('volunteers/<int:volunteer_id>/delete/', views.volunteer_delete, name='volunteer_delete'),
|
||||
path('volunteers/<int:volunteer_id>/assign-event/', views.volunteer_assign_event, name='volunteer_assign_event'),
|
||||
path('volunteers/assignment/<int:assignment_id>/remove/', views.volunteer_remove_event, name='volunteer_remove_event'),
|
||||
path('volunteers/search/json/', views.volunteer_search_json, name='volunteer_search_json'),
|
||||
path('volunteers/bulk-sms/', views.volunteer_bulk_send_sms, name='volunteer_bulk_send_sms'),
|
||||
path('events/<int:event_id>/volunteer/add/', views.volunteer_event_create, name='event_add_volunteer'), # Changed to volunteer_event_create
|
||||
path('events/volunteer/<int:pk>/delete/', views.volunteer_event_delete, name='event_remove_volunteer'), # Changed to pk and volunteer_event_delete
|
||||
path('events/<int:event_id>/volunteer/add/', views.event_add_volunteer, name='event_add_volunteer'),
|
||||
path('events/volunteer/<int:assignment_id>/delete/', views.event_remove_volunteer, name='event_remove_volunteer'),
|
||||
|
||||
# Door Visits
|
||||
path('door-visits/', views.door_visits, name='door_visits'),
|
||||
path('door-visits/log/', views.create_interaction_for_voter, name='log_door_visit'), # Changed to create_interaction_for_voter
|
||||
path('door-visits/log/', views.log_door_visit, name='log_door_visit'),
|
||||
path('door-visits/history/', views.door_visit_history, name='door_visit_history'),
|
||||
|
||||
# Call Queue
|
||||
path('call-queue/', views.call_queue, name='call_queue'),
|
||||
path('call-queue/<int:pk>/complete/', views.complete_call, name='complete_call'), # Changed to pk
|
||||
path('call-queue/<int:pk>/delete/', views.scheduled_call_delete, name='delete_call'), # Changed to pk and scheduled_call_delete
|
||||
path('call-queue/<int:call_id>/complete/', views.complete_call, name='complete_call'),
|
||||
path('call-queue/<int:call_id>/delete/', views.delete_call, name='delete_call'),
|
||||
path('profile/', views.profile, name='profile'),
|
||||
]
|
||||
2500
core/views.py
2500
core/views.py
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,125 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
file_path = 'core/views.py'
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
old_code = 'visited_households[key]["voters_at_address"].add(f"{v.first_name} {v.last_name}")'
|
||||
new_code = 'visited_households[key]["voters_at_address"].add((v.id, f"{v.first_name} {v.last_name}"))'
|
||||
# Define the new function as a single string
|
||||
new_func = """def door_visit_history(request):
|
||||
"""
|
||||
Shows a distinct list of Door visit interactions for addresses.
|
||||
"""
|
||||
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)
|
||||
|
||||
# Date filter
|
||||
start_date = request.GET.get("start_date")
|
||||
end_date = request.GET.get("end_date")
|
||||
|
||||
# Get all "Door Visit" interactions for this tenant
|
||||
interactions = Interaction.objects.filter(
|
||||
voter__tenant=tenant,
|
||||
type__name="Door Visit"
|
||||
).select_related("voter", "volunteer")
|
||||
|
||||
if old_code in content:
|
||||
new_content = content.replace(old_code, new_code)
|
||||
if start_date or end_date:
|
||||
try:
|
||||
if start_date:
|
||||
d = parse_date(start_date)
|
||||
if d:
|
||||
start_dt = timezone.make_aware(datetime.combine(d, time.min))
|
||||
interactions = interactions.filter(date__gte=start_dt)
|
||||
if end_date:
|
||||
d = parse_date(end_date)
|
||||
if d:
|
||||
# Use lt with next day to capture everything on the end_date
|
||||
end_dt = timezone.make_aware(datetime.combine(d + timedelta(days=1), time.min))
|
||||
interactions = interactions.filter(date__lt=end_dt)
|
||||
except Exception as e:
|
||||
logger.error(f"Error filtering door visit history by date: {e}")
|
||||
|
||||
# Summary of counts per volunteer
|
||||
# Grouping by household (unique address)
|
||||
visited_households = {}
|
||||
volunteer_counts = {}
|
||||
|
||||
for interaction in interactions.order_by("-date"):
|
||||
v = interaction.voter
|
||||
addr = v.address.strip() if v.address else f"{v.address_street}, {v.city}, {v.state} {v.zip_code}".strip(", ")
|
||||
if not addr:
|
||||
continue
|
||||
|
||||
key = addr.lower()
|
||||
|
||||
if key not in visited_households:
|
||||
# Calculate volunteer summary - only once per household
|
||||
v_obj = interaction.volunteer
|
||||
v_name = f"{v_obj.first_name} {v_obj.last_name}".strip() or v_obj.email if v_obj else "N/A"
|
||||
volunteer_counts[v_name] = volunteer_counts.get(v_name, 0) + 1
|
||||
|
||||
# Parse street name and number for sorting
|
||||
street_number = ""
|
||||
street_name = v.address_street or ""
|
||||
match = re.match(r'^(\d+)\s+(.*)$', street_name)
|
||||
if match:
|
||||
street_number = match.group(1)
|
||||
street_name = match.group(2)
|
||||
|
||||
try:
|
||||
street_number_sort = int(street_number)
|
||||
except ValueError:
|
||||
street_number_sort = 0
|
||||
|
||||
visited_households[key] = {
|
||||
'address_display': addr,
|
||||
'address_street': v.address_street,
|
||||
'city': v.city,
|
||||
'state': v.state,
|
||||
'zip_code': v.zip_code,
|
||||
'neighborhood': v.neighborhood,
|
||||
'district': v.district,
|
||||
'latitude': float(v.latitude) if v.latitude else None,
|
||||
'longitude': float(v.longitude) if v.longitude else None,
|
||||
'street_name_sort': street_name.lower(),
|
||||
'street_number_sort': street_number_sort,
|
||||
'last_visit_date': interaction.date,
|
||||
'target_voters': [],
|
||||
'voters_json': []
|
||||
}
|
||||
|
||||
visited_households[key]["voters_json"].append({'id': v.id, 'name': f"{v.first_name} {v.last_name}"})
|
||||
visited_households[key]['target_voters'].append(v)
|
||||
|
||||
# Sort volunteer counts by total (descending)
|
||||
sorted_volunteer_counts = sorted(volunteer_counts.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
history_list = list(visited_households.values())
|
||||
history_list.sort(key=lambda x: x["last_visit_date"], reverse=True)
|
||||
|
||||
paginator = Paginator(history_list, 50)
|
||||
page_number = request.GET.get("page")
|
||||
history_page = paginator.get_page(page_number)
|
||||
|
||||
context = {
|
||||
"selected_tenant": tenant,
|
||||
"history": history_page,
|
||||
"start_date": start_date, "end_date": end_date,
|
||||
"volunteer_counts": sorted_volunteer_counts,
|
||||
}
|
||||
return render(request, "core/door_visit_history.html", context)
|
||||
"""
|
||||
|
||||
# Use regex to find and replace the function
|
||||
pattern = r'def door_visit_history\(request\):.*?return render\(request, "core/door_visit_history\.html", context\)'
|
||||
new_content = re.sub(pattern, new_func, content, flags=re.DOTALL)
|
||||
|
||||
if new_content != content:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(new_content)
|
||||
print("Successfully patched core/views.py")
|
||||
print("Successfully updated door_visit_history")
|
||||
else:
|
||||
print("Could not find the target line in core/views.py")
|
||||
print("Could not find function to replace")
|
||||
Loading…
x
Reference in New Issue
Block a user