Autosave: 20260208-213524
This commit is contained in:
parent
c92857d73b
commit
8de72675e5
Binary file not shown.
Binary file not shown.
@ -456,7 +456,12 @@ class DoorVisitLogForm(forms.Form):
|
||||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||
label="Follow Up"
|
||||
)
|
||||
follow_up_voter = forms.CharField( required=False, widget=forms.Select(attrs={"class": "form-select"}), label="Voter to Follow Up")
|
||||
follow_up_voter = forms.ChoiceField(choices=[], required=False, widget=forms.Select(attrs={"class": "form-select"}), label="Voter to Follow Up")
|
||||
|
||||
def __init__(self, *args, voter_choices=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if voter_choices:
|
||||
self.fields["follow_up_voter"].choices = voter_choices
|
||||
call_notes = forms.CharField(
|
||||
widget=forms.Textarea(attrs={"class": "form-control", "rows": 2}),
|
||||
required=False,
|
||||
|
||||
@ -78,8 +78,10 @@
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold text-dark">{{ household.address_street }}</div>
|
||||
<a href="{% url 'log_door_visit' %}?address_street={{ household.address_street|urlencode }}&city={{ household.city|urlencode }}&state={{ household.state|urlencode }}&zip_code={{ household.zip_code|urlencode }}&next_query_string={{ request.GET.urlencode|urlencode }}" class="text-decoration-none">
|
||||
<div class="fw-bold text-dark hover-underline">{{ household.address_street }}</div>
|
||||
<div class="small text-muted">{{ household.city }}</div>
|
||||
</a>
|
||||
<div class="d-md-none mt-1">
|
||||
{% if household.neighborhood %}
|
||||
<span class="badge border border-primary-subtle bg-primary-subtle text-primary fw-medium px-2 py-1 small">
|
||||
@ -128,12 +130,12 @@
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
{% if households.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page=1{% if district_filter %}&district={{ district_filter }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter }}{% endif %}{% if address_filter %}&address={{ address_filter }}{% endif %}" aria-label="First">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page=1{% if district_filter %}&district={{ district_filter|urlencode }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter|urlencode }}{% endif %}{% if address_filter %}&address={{ address_filter|urlencode }}{% endif %}" aria-label="First">
|
||||
<i class="bi bi-chevron-double-left small"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page={{ households.previous_page_number }}{% if district_filter %}&district={{ district_filter }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter }}{% endif %}{% if address_filter %}&address={{ address_filter }}{% endif %}" aria-label="Previous">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page={{ households.previous_page_number }}{% if district_filter %}&district={{ district_filter|urlencode }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter|urlencode }}{% endif %}{% if address_filter %}&address={{ address_filter|urlencode }}{% endif %}" aria-label="Previous">
|
||||
<i class="bi bi-chevron-left small"></i>
|
||||
</a>
|
||||
</li>
|
||||
@ -143,12 +145,12 @@
|
||||
|
||||
{% if households.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page={{ households.next_page_number }}{% if district_filter %}&district={{ district_filter }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter }}{% endif %}{% if address_filter %}&address={{ address_filter }}{% endif %}" aria-label="Next">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page={{ households.next_page_number }}{% if district_filter %}&district={{ district_filter|urlencode }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter|urlencode }}{% endif %}{% if address_filter %}&address={{ address_filter|urlencode }}{% endif %}" aria-label="Next">
|
||||
<i class="bi bi-chevron-right small"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page={{ households.paginator.num_pages }}{% if district_filter %}&district={{ district_filter }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter }}{% endif %}{% if address_filter %}&address={{ address_filter }}{% endif %}" aria-label="Last">
|
||||
<a class="page-link rounded-circle mx-1 border-0 bg-light text-dark" href="?page={{ households.paginator.num_pages }}{% if district_filter %}&district={{ district_filter|urlencode }}{% endif %}{% if neighborhood_filter %}&neighborhood={{ neighborhood_filter|urlencode }}{% endif %}{% if address_filter %}&address={{ address_filter|urlencode }}{% endif %}" aria-label="Last">
|
||||
<i class="bi bi-chevron-double-right small"></i>
|
||||
</a>
|
||||
</li>
|
||||
@ -263,8 +265,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<!-- Google Maps JS - Using global config matching voter page -->
|
||||
{% block extra_js %}
|
||||
<!-- Google Maps JS -->
|
||||
{% if GOOGLE_MAPS_API_KEY %}
|
||||
<script src="https://maps.googleapis.com/maps/api/js?key={{ GOOGLE_MAPS_API_KEY }}"></script>
|
||||
{% endif %}
|
||||
@ -302,7 +306,17 @@
|
||||
});
|
||||
|
||||
marker.addListener('click', function() {
|
||||
infowindow.setContent('<strong>' + item.address + '</strong><br>' + item.voters);
|
||||
var logUrl = "{% url 'log_door_visit' %}?address_street=" + encodeURIComponent(item.address_street) +
|
||||
"&city=" + encodeURIComponent(item.city) +
|
||||
"&state=" + encodeURIComponent(item.state) +
|
||||
"&zip_code=" + encodeURIComponent(item.zip_code) +
|
||||
"&next_query_string=" + encodeURIComponent("{{ request.GET.urlencode|escapejs }}") + "" + "&source=map";
|
||||
|
||||
var content = '<strong>' + item.address + '</strong><br>' +
|
||||
item.voters + '<br><br>' +
|
||||
'<a href="' + logUrl + '" class="btn btn-sm btn-primary text-white px-3 py-1">Log Visit</a>';
|
||||
|
||||
infowindow.setContent(content);
|
||||
infowindow.open(map, marker);
|
||||
});
|
||||
|
||||
@ -317,13 +331,24 @@
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Automatically open map if requested
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('open_map') === '1') {
|
||||
var mapModalElement = document.getElementById('mapModal');
|
||||
if (mapModalElement) {
|
||||
var mapModal = new bootstrap.Modal(mapModalElement);
|
||||
mapModal.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger map initialization when modal is shown
|
||||
var mapModal = document.getElementById('mapModal');
|
||||
if (mapModal) {
|
||||
mapModal.addEventListener('shown.bs.modal', function () {
|
||||
var mapModalElement = document.getElementById('mapModal');
|
||||
if (mapModalElement) {
|
||||
mapModalElement.addEventListener('shown.bs.modal', function () {
|
||||
if (!map) {
|
||||
initMap();
|
||||
} else {
|
||||
if (window.google && window.google.maps) {
|
||||
google.maps.event.trigger(map, 'resize');
|
||||
if (markers.length > 0) {
|
||||
var bounds = new google.maps.LatLngBounds();
|
||||
@ -333,6 +358,7 @@
|
||||
map.fitBounds(bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -340,20 +366,23 @@
|
||||
if (logVisitModal) {
|
||||
logVisitModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
if (!button) return;
|
||||
|
||||
var address = button.getAttribute('data-address');
|
||||
var city = button.getAttribute('data-city');
|
||||
var state = button.getAttribute('data-state');
|
||||
var zip = button.getAttribute('data-zip');
|
||||
|
||||
document.getElementById('modalAddressDisplay').textContent = address + ', ' + city + ', ' + state + ' ' + zip;
|
||||
document.getElementById('modal_address_street').value = address;
|
||||
document.getElementById('modal_city').value = city;
|
||||
document.getElementById('modal_state').value = state;
|
||||
document.getElementById('modal_zip_code').value = zip;
|
||||
document.getElementById('modalAddressDisplay').textContent = (address || '') + ', ' + (city || '') + ', ' + (state || '') + ' ' + (zip || '');
|
||||
document.getElementById('modal_address_street').value = address || '';
|
||||
document.getElementById('modal_city').value = city || '';
|
||||
document.getElementById('modal_state').value = state || '';
|
||||
document.getElementById('modal_zip_code').value = zip || '';
|
||||
|
||||
// Populate voters dropdown
|
||||
var votersJson = button.getAttribute('data-voters');
|
||||
if (votersJson) {
|
||||
try {
|
||||
var voters = JSON.parse(votersJson);
|
||||
var voterSelect = document.getElementById('{{ visit_form.follow_up_voter.id_for_label }}');
|
||||
if (voterSelect) {
|
||||
@ -365,6 +394,9 @@
|
||||
voterSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing voters JSON:", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
150
core/templates/core/log_door_visit.html
Normal file
150
core/templates/core/log_door_visit.html
Normal file
@ -0,0 +1,150 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-8">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<a href="{{ redirect_url }}" class="btn btn-outline-secondary rounded-circle me-3">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="h3 fw-bold mb-0">Log Door Visit</h1>
|
||||
<p class="text-muted mb-0">Record interaction for this household</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<div class="bg-primary-subtle p-4 rounded-3 mb-4 border border-primary-subtle">
|
||||
<div class="small text-uppercase fw-bold text-primary mb-1">Household Address</div>
|
||||
<div class="h4 mb-0 fw-bold text-dark">{{ address_street }}</div>
|
||||
<div class="text-muted">{{ city }}, {{ state }} {{ zip_code }}</div>
|
||||
</div>
|
||||
|
||||
<form action="{% url 'log_door_visit' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="address_street" value="{{ address_street }}">
|
||||
<input type="hidden" name="city" value="{{ city }}">
|
||||
<input type="hidden" name="state" value="{{ state }}">
|
||||
<input type="hidden" name="zip_code" value="{{ zip_code }}">
|
||||
<input type="hidden" name="next_query_string" value="{{ next_query_string }}">
|
||||
<input type="hidden" name="source" value="{{ source }}">
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold text-primary small text-uppercase">Visit Outcome</label>
|
||||
<div class="row g-2">
|
||||
{% for radio in visit_form.outcome %}
|
||||
<div class="col-12 col-md-4">
|
||||
{{ radio.tag }}
|
||||
<label class="btn btn-outline-primary w-100 h-100 d-flex align-items-center justify-content-center text-center py-3 px-2" for="{{ radio.id_for_label }}">
|
||||
{{ radio.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="{{ visit_form.notes.id_for_label }}" class="form-label fw-bold text-primary small text-uppercase">Notes / Conversation Summary</label>
|
||||
{{ visit_form.notes }}
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<label class="form-label fw-bold text-primary small text-uppercase">Support Status</label>
|
||||
{{ visit_form.candidate_support }}
|
||||
</div>
|
||||
<div class="col-12 col-md-6 d-flex align-items-end">
|
||||
<div class="form-check mb-2">
|
||||
{{ visit_form.wants_yard_sign }}
|
||||
<label class="form-check-label fw-bold text-dark" for="{{ visit_form.wants_yard_sign.id_for_label }}">
|
||||
Wants a Yard Sign
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-5">
|
||||
|
||||
<div class="bg-light p-4 rounded-4">
|
||||
<div class="form-check mb-4">
|
||||
{{ visit_form.follow_up }}
|
||||
<label class="form-check-label h6 fw-bold text-dark mb-0" for="{{ visit_form.follow_up.id_for_label }}">
|
||||
Schedule a Follow-up Call
|
||||
</label>
|
||||
</div>
|
||||
<div id="callNotesContainer" {% if not visit_form.follow_up.value %}style="display: none;"{% endif %}>
|
||||
<div class="mb-3">
|
||||
<label for="{{ visit_form.follow_up_voter.id_for_label }}" class="form-label fw-bold text-primary small text-uppercase">Recipient of the Call</label>
|
||||
{{ visit_form.follow_up_voter }}
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label for="{{ visit_form.call_notes.id_for_label }}" class="form-label fw-bold text-primary small text-uppercase">Call Queue Notes</label>
|
||||
{{ visit_form.call_notes }}
|
||||
<div class="form-text small">These notes will be added to the call queue for the default caller.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 d-grid d-md-flex justify-content-md-end gap-2">
|
||||
<a href="{{ redirect_url }}" class="btn btn-light px-5 py-3 rounded-3 fw-bold">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary px-5 py-3 rounded-3 fw-bold shadow">Save Visit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h5 class="fw-bold text-dark mb-3">Targeted Voters at this Address</h5>
|
||||
<div class="list-group list-group-flush shadow-sm rounded-4 overflow-hidden">
|
||||
{% for voter in voters %}
|
||||
<a href="{% url 'voter_detail' voter.id %}" class="list-group-item list-group-item-action py-3 border-0">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="fw-bold text-primary">{{ voter.first_name }} {{ voter.last_name }}</div>
|
||||
<div class="small text-muted">Voter ID: {{ voter.voter_id|default:"N/A" }}</div>
|
||||
</div>
|
||||
<i class="bi bi-chevron-right text-muted"></i>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const followUpCheckbox = document.getElementById('{{ visit_form.follow_up.id_for_label }}');
|
||||
const callNotesContainer = document.getElementById('callNotesContainer');
|
||||
if (followUpCheckbox && callNotesContainer) {
|
||||
followUpCheckbox.addEventListener('change', function() {
|
||||
callNotesContainer.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.btn-outline-primary {
|
||||
color: #0d6efd !important;
|
||||
border-color: #0d6efd !important;
|
||||
}
|
||||
.btn-outline-primary:hover, .btn-check:checked + .btn-outline-primary {
|
||||
background-color: #0d6efd !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
.bg-primary-subtle {
|
||||
background-color: #e7f1ff;
|
||||
}
|
||||
.text-primary {
|
||||
color: #0d6efd !important;
|
||||
}
|
||||
.border-primary-subtle {
|
||||
border-color: #9ec5fe !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -1655,7 +1655,7 @@ def door_visits(request):
|
||||
{
|
||||
'lat': h['latitude'],
|
||||
'lng': h['longitude'],
|
||||
'address': f"{h['address_street']}, {h['city']}, {h['state']}",
|
||||
'address_street': h['address_street'], 'city': h['city'], 'state': h['state'], 'zip_code': h['zip_code'], 'address': f"{h['address_street']}, {h['city']}, {h['state']}",
|
||||
'voters': ", ".join([f"{v.first_name} {v.last_name}" for v in h['target_voters']])
|
||||
}
|
||||
for h in households_list if h['latitude'] and h['longitude']
|
||||
@ -1672,7 +1672,7 @@ def door_visits(request):
|
||||
'neighborhood_filter': neighborhood_filter,
|
||||
'address_filter': address_filter,
|
||||
'map_data_json': json.dumps(map_data),
|
||||
'google_maps_api_key': getattr(settings, 'GOOGLE_MAPS_API_KEY', ''),
|
||||
'GOOGLE_MAPS_API_KEY': getattr(settings, 'GOOGLE_MAPS_API_KEY', ''),
|
||||
'visit_form': DoorVisitLogForm(),
|
||||
}
|
||||
return render(request, 'core/door_visits.html', context)
|
||||
@ -1682,6 +1682,7 @@ def log_door_visit(request):
|
||||
"""
|
||||
Mark all targeted voters at a specific address as visited, update their flags,
|
||||
and create interaction records.
|
||||
Can also render a standalone page for logging a visit.
|
||||
"""
|
||||
selected_tenant_id = request.session.get("tenant_id")
|
||||
if not selected_tenant_id:
|
||||
@ -1691,22 +1692,53 @@ def log_door_visit(request):
|
||||
campaign_settings, _ = CampaignSettings.objects.get_or_create(tenant=tenant)
|
||||
|
||||
# Capture query string for redirecting back with filters
|
||||
next_qs = request.POST.get("next_query_string", "")
|
||||
next_qs = request.POST.get("next_query_string", request.GET.get("next_query_string", ""))
|
||||
source = request.POST.get("source", request.GET.get("source", ""))
|
||||
|
||||
redirect_url = reverse("door_visits")
|
||||
|
||||
# Build redirect URL
|
||||
redirect_params = []
|
||||
if next_qs:
|
||||
redirect_url += f"?{next_qs}"
|
||||
redirect_params.append(next_qs)
|
||||
if source == "map":
|
||||
redirect_params.append("open_map=1")
|
||||
|
||||
if redirect_params:
|
||||
redirect_url += "?" + "&".join(redirect_params)
|
||||
|
||||
# Get address components from POST or GET
|
||||
address_street = request.POST.get("address_street", request.GET.get("address_street"))
|
||||
city = request.POST.get("city", request.GET.get("city"))
|
||||
state = request.POST.get("state", request.GET.get("state"))
|
||||
zip_code = request.POST.get("zip_code", request.GET.get("zip_code"))
|
||||
|
||||
if not address_street:
|
||||
messages.warning(request, "No address provided.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
# Find targeted voters at this exact address
|
||||
voters = Voter.objects.filter(
|
||||
tenant=tenant,
|
||||
address_street=address_street,
|
||||
city=city,
|
||||
state=state,
|
||||
zip_code=zip_code,
|
||||
is_targeted=True
|
||||
)
|
||||
|
||||
if not voters.exists() and request.method == "POST":
|
||||
messages.warning(request, f"No targeted voters found at {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
voter_choices = [(v.id, f"{v.first_name} {v.last_name}") for v in voters]
|
||||
|
||||
# Get the volunteer linked to the current user
|
||||
volunteer = Volunteer.objects.filter(user=request.user, tenant=tenant).first()
|
||||
|
||||
if request.method == "POST":
|
||||
form = DoorVisitLogForm(request.POST)
|
||||
form = DoorVisitLogForm(request.POST, voter_choices=voter_choices)
|
||||
if form.is_valid():
|
||||
address_street = request.POST.get("address_street")
|
||||
city = request.POST.get("city")
|
||||
state = request.POST.get("state")
|
||||
zip_code = request.POST.get("zip_code")
|
||||
|
||||
outcome = form.cleaned_data["outcome"]
|
||||
notes = form.cleaned_data["notes"]
|
||||
wants_yard_sign = form.cleaned_data["wants_yard_sign"]
|
||||
@ -1722,25 +1754,11 @@ def log_door_visit(request):
|
||||
except:
|
||||
tz = zoneinfo.ZoneInfo("America/Chicago")
|
||||
|
||||
interaction_date = timezone.now().astimezone(tz);
|
||||
interaction_date = timezone.now().astimezone(tz)
|
||||
|
||||
# Get or create InteractionType
|
||||
interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="Door Visit")
|
||||
|
||||
# Find targeted voters at this exact address
|
||||
voters = Voter.objects.filter(
|
||||
tenant=tenant,
|
||||
address_street=address_street,
|
||||
city=city,
|
||||
state=state,
|
||||
zip_code=zip_code,
|
||||
is_targeted=True
|
||||
)
|
||||
|
||||
if not voters.exists():
|
||||
messages.warning(request, f"No targeted voters found at {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
# Get default caller for follow-ups
|
||||
default_caller = None
|
||||
if follow_up:
|
||||
@ -1784,10 +1802,26 @@ def log_door_visit(request):
|
||||
messages.success(request, f"Door visit logged and follow-up call scheduled for {address_street}.")
|
||||
else:
|
||||
messages.success(request, f"Door visit logged for {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
messages.error(request, "There was an error in the visit log form.")
|
||||
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
# GET request: render standalone page
|
||||
form = DoorVisitLogForm(voter_choices=voter_choices)
|
||||
context = {
|
||||
'selected_tenant': tenant,
|
||||
'visit_form': form,
|
||||
'address_street': address_street,
|
||||
'city': city,
|
||||
'state': state,
|
||||
'zip_code': zip_code,
|
||||
'voters': voters,
|
||||
'next_query_string': next_qs,
|
||||
'source': source,
|
||||
'redirect_url': redirect_url,
|
||||
}
|
||||
return render(request, 'core/log_door_visit.html', context)
|
||||
|
||||
def door_visit_history(request):
|
||||
"""
|
||||
|
||||
144
core_views_log_visit.py
Normal file
144
core_views_log_visit.py
Normal file
@ -0,0 +1,144 @@
|
||||
def log_door_visit(request):
|
||||
"""
|
||||
Mark all targeted voters at a specific address as visited, update their flags,
|
||||
and create interaction records.
|
||||
Can also render a standalone page for logging a visit.
|
||||
"""
|
||||
selected_tenant_id = request.session.get("tenant_id")
|
||||
if not selected_tenant_id:
|
||||
return redirect("index")
|
||||
|
||||
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
|
||||
campaign_settings, _ = CampaignSettings.objects.get_or_create(tenant=tenant)
|
||||
|
||||
# Capture query string for redirecting back with filters
|
||||
next_qs = request.POST.get("next_query_string", request.GET.get("next_query_string", ""))
|
||||
source = request.POST.get("source", request.GET.get("source", ""))
|
||||
|
||||
redirect_url = reverse("door_visits")
|
||||
|
||||
# Build redirect URL
|
||||
redirect_params = []
|
||||
if next_qs:
|
||||
redirect_params.append(next_qs)
|
||||
if source == "map":
|
||||
redirect_params.append("open_map=1")
|
||||
|
||||
if redirect_params:
|
||||
redirect_url += "?" + "&".join(redirect_params)
|
||||
|
||||
# Get address components from POST or GET
|
||||
address_street = request.POST.get("address_street", request.GET.get("address_street"))
|
||||
city = request.POST.get("city", request.GET.get("city"))
|
||||
state = request.POST.get("state", request.GET.get("state"))
|
||||
zip_code = request.POST.get("zip_code", request.GET.get("zip_code"))
|
||||
|
||||
if not address_street:
|
||||
messages.warning(request, "No address provided.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
# Find targeted voters at this exact address
|
||||
voters = Voter.objects.filter(
|
||||
tenant=tenant,
|
||||
address_street=address_street,
|
||||
city=city,
|
||||
state=state,
|
||||
zip_code=zip_code,
|
||||
is_targeted=True
|
||||
)
|
||||
|
||||
if not voters.exists() and request.method == "POST":
|
||||
messages.warning(request, f"No targeted voters found at {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
voter_choices = [(v.id, f"{v.first_name} {v.last_name}") for v in voters]
|
||||
|
||||
# Get the volunteer linked to the current user
|
||||
volunteer = Volunteer.objects.filter(user=request.user, tenant=tenant).first()
|
||||
|
||||
if request.method == "POST":
|
||||
form = DoorVisitLogForm(request.POST, voter_choices=voter_choices)
|
||||
if form.is_valid():
|
||||
outcome = form.cleaned_data["outcome"]
|
||||
notes = form.cleaned_data["notes"]
|
||||
wants_yard_sign = form.cleaned_data["wants_yard_sign"]
|
||||
candidate_support = form.cleaned_data["candidate_support"]
|
||||
follow_up = form.cleaned_data["follow_up"]
|
||||
follow_up_voter_id = form.cleaned_data.get("follow_up_voter")
|
||||
call_notes = form.cleaned_data["call_notes"]
|
||||
|
||||
# Determine date/time in campaign timezone
|
||||
campaign_tz_name = campaign_settings.timezone or "America/Chicago"
|
||||
try:
|
||||
tz = zoneinfo.ZoneInfo(campaign_tz_name)
|
||||
except:
|
||||
tz = zoneinfo.ZoneInfo("America/Chicago")
|
||||
|
||||
interaction_date = timezone.now().astimezone(tz)
|
||||
|
||||
# Get or create InteractionType
|
||||
interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="Door Visit")
|
||||
|
||||
# Get default caller for follow-ups
|
||||
default_caller = None
|
||||
if follow_up:
|
||||
default_caller = Volunteer.objects.filter(tenant=tenant, is_default_caller=True).first()
|
||||
|
||||
for voter in voters:
|
||||
# 1) Update voter flags
|
||||
voter.door_visit = True
|
||||
|
||||
# 2) If "Wants a Yard Sign" checkbox is selected
|
||||
if wants_yard_sign:
|
||||
voter.yard_sign = "wants"
|
||||
|
||||
# 3) Update support status if Supporting or Not Supporting
|
||||
if candidate_support in ["supporting", "not_supporting"]:
|
||||
voter.candidate_support = candidate_support
|
||||
|
||||
voter.save()
|
||||
|
||||
# 4) Create interaction
|
||||
Interaction.objects.create(
|
||||
voter=voter,
|
||||
volunteer=volunteer,
|
||||
type=interaction_type,
|
||||
date=interaction_date,
|
||||
description=outcome,
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# 5) Create ScheduledCall if follow_up is checked and this is the selected voter
|
||||
if follow_up and follow_up_voter_id and str(voter.id) == follow_up_voter_id:
|
||||
ScheduledCall.objects.create(
|
||||
tenant=tenant,
|
||||
voter=voter,
|
||||
volunteer=default_caller,
|
||||
comments=call_notes,
|
||||
status="pending"
|
||||
)
|
||||
|
||||
if follow_up:
|
||||
messages.success(request, f"Door visit logged and follow-up call scheduled for {address_street}.")
|
||||
else:
|
||||
messages.success(request, f"Door visit logged for {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
messages.error(request, "There was an error in the visit log form.")
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
# GET request: render standalone page
|
||||
form = DoorVisitLogForm(voter_choices=voter_choices)
|
||||
context = {
|
||||
'selected_tenant': tenant,
|
||||
'visit_form': form,
|
||||
'address_street': address_street,
|
||||
'city': city,
|
||||
'state': state,
|
||||
'zip_code': zip_code,
|
||||
'voters': voters,
|
||||
'next_query_string': next_qs,
|
||||
'source': source,
|
||||
'redirect_url': redirect_url,
|
||||
}
|
||||
return render(request, 'core/log_door_visit.html', context)
|
||||
@ -1,125 +1,152 @@
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import django
|
||||
|
||||
file_path = 'core/views.py'
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
django.setup()
|
||||
|
||||
# Define the new function as a single string
|
||||
new_func = """def door_visit_history(request):
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from django.utils import timezone
|
||||
from core.models import Tenant, CampaignSettings, Volunteer, Voter, InteractionType, Interaction, ScheduledCall
|
||||
from core.forms import DoorVisitLogForm
|
||||
import zoneinfo
|
||||
|
||||
def log_door_visit(request):
|
||||
"""
|
||||
Shows a distinct list of Door visit interactions for addresses.
|
||||
Mark all targeted voters at a specific address as visited, update their flags,
|
||||
and create interaction records.
|
||||
Can also render a standalone page for logging a visit.
|
||||
"""
|
||||
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)
|
||||
campaign_settings, _ = CampaignSettings.objects.get_or_create(tenant=tenant)
|
||||
|
||||
# Date filter
|
||||
start_date = request.GET.get("start_date")
|
||||
end_date = request.GET.get("end_date")
|
||||
# Capture query string for redirecting back with filters
|
||||
next_qs = request.POST.get("next_query_string", request.GET.get("next_query_string", ""))
|
||||
redirect_url = reverse("door_visits")
|
||||
if next_qs:
|
||||
redirect_url += f"?{next_qs}"
|
||||
|
||||
# Get all "Door Visit" interactions for this tenant
|
||||
interactions = Interaction.objects.filter(
|
||||
voter__tenant=tenant,
|
||||
type__name="Door Visit"
|
||||
).select_related("voter", "volunteer")
|
||||
# Get address components from POST or GET
|
||||
address_street = request.POST.get("address_street", request.GET.get("address_street"))
|
||||
city = request.POST.get("city", request.GET.get("city"))
|
||||
state = request.POST.get("state", request.GET.get("state"))
|
||||
zip_code = request.POST.get("zip_code", request.GET.get("zip_code"))
|
||||
|
||||
if start_date or end_date:
|
||||
if not address_street:
|
||||
messages.warning(request, "No address provided.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
# Find targeted voters at this exact address
|
||||
voters = Voter.objects.filter(
|
||||
tenant=tenant,
|
||||
address_street=address_street,
|
||||
city=city,
|
||||
state=state,
|
||||
zip_code=zip_code,
|
||||
is_targeted=True
|
||||
)
|
||||
|
||||
if not voters.exists() and request.method == "POST":
|
||||
messages.warning(request, f"No targeted voters found at {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
voter_choices = [(v.id, f"{v.first_name} {v.last_name}") for v in voters]
|
||||
|
||||
# Get the volunteer linked to the current user
|
||||
volunteer = Volunteer.objects.filter(user=request.user, tenant=tenant).first()
|
||||
|
||||
if request.method == "POST":
|
||||
form = DoorVisitLogForm(request.POST, voter_choices=voter_choices)
|
||||
if form.is_valid():
|
||||
outcome = form.cleaned_data["outcome"]
|
||||
notes = form.cleaned_data["notes"]
|
||||
wants_yard_sign = form.cleaned_data["wants_yard_sign"]
|
||||
candidate_support = form.cleaned_data["candidate_support"]
|
||||
follow_up = form.cleaned_data["follow_up"]
|
||||
follow_up_voter_id = form.cleaned_data.get("follow_up_voter")
|
||||
call_notes = form.cleaned_data["call_notes"]
|
||||
|
||||
# Determine date/time in campaign timezone
|
||||
campaign_tz_name = campaign_settings.timezone or "America/Chicago"
|
||||
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}")
|
||||
tz = zoneinfo.ZoneInfo(campaign_tz_name)
|
||||
except:
|
||||
tz = zoneinfo.ZoneInfo("America/Chicago")
|
||||
|
||||
# Summary of counts per volunteer
|
||||
# Grouping by household (unique address)
|
||||
visited_households = {}
|
||||
volunteer_counts = {}
|
||||
interaction_date = timezone.now().astimezone(tz)
|
||||
|
||||
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
|
||||
# Get or create InteractionType
|
||||
interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="Door Visit")
|
||||
|
||||
key = addr.lower()
|
||||
# Get default caller for follow-ups
|
||||
default_caller = None
|
||||
if follow_up:
|
||||
default_caller = Volunteer.objects.filter(tenant=tenant, is_default_caller=True).first()
|
||||
|
||||
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
|
||||
for voter in voters:
|
||||
# 1) Update voter flags
|
||||
voter.door_visit = True
|
||||
|
||||
# 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)
|
||||
# 2) If "Wants a Yard Sign" checkbox is selected
|
||||
if wants_yard_sign:
|
||||
voter.yard_sign = "wants"
|
||||
|
||||
try:
|
||||
street_number_sort = int(street_number)
|
||||
except ValueError:
|
||||
street_number_sort = 0
|
||||
# 3) Update support status if Supporting or Not Supporting
|
||||
if candidate_support in ["supporting", "not_supporting"]:
|
||||
voter.candidate_support = candidate_support
|
||||
|
||||
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': []
|
||||
}
|
||||
voter.save()
|
||||
|
||||
visited_households[key]["voters_json"].append({'id': v.id, 'name': f"{v.first_name} {v.last_name}"})
|
||||
visited_households[key]['target_voters'].append(v)
|
||||
# 4) Create interaction
|
||||
Interaction.objects.create(
|
||||
voter=voter,
|
||||
volunteer=volunteer,
|
||||
type=interaction_type,
|
||||
date=interaction_date,
|
||||
description=outcome,
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# 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)
|
||||
# 5) Create ScheduledCall if follow_up is checked and this is the selected voter
|
||||
if follow_up and follow_up_voter_id and str(voter.id) == follow_up_voter_id:
|
||||
ScheduledCall.objects.create(
|
||||
tenant=tenant,
|
||||
voter=voter,
|
||||
volunteer=default_caller,
|
||||
comments=call_notes,
|
||||
status="pending"
|
||||
)
|
||||
|
||||
if follow_up:
|
||||
messages.success(request, f"Door visit logged and follow-up call scheduled for {address_street}.")
|
||||
else:
|
||||
messages.success(request, f"Door visit logged for {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
if request.headers.get('x-requested-with') == 'XMLHttpRequest':
|
||||
# If it's the modal, we might want to handle it differently,
|
||||
# but currently it's a standard POST from modal.
|
||||
pass
|
||||
messages.error(request, "There was an error in the visit log form.")
|
||||
else:
|
||||
# GET request: render standalone page
|
||||
form = DoorVisitLogForm(voter_choices=voter_choices)
|
||||
context = {
|
||||
"selected_tenant": tenant,
|
||||
"history": history_page,
|
||||
"start_date": start_date, "end_date": end_date,
|
||||
"volunteer_counts": sorted_volunteer_counts,
|
||||
'selected_tenant': tenant,
|
||||
'visit_form': form,
|
||||
'address_street': address_street,
|
||||
'city': city,
|
||||
'state': state,
|
||||
'zip_code': zip_code,
|
||||
'voters': voters,
|
||||
'next_query_string': next_qs,
|
||||
}
|
||||
return render(request, "core/door_visit_history.html", context)
|
||||
"""
|
||||
return render(request, 'core/log_door_visit.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)
|
||||
return redirect(redirect_url)
|
||||
|
||||
if new_content != content:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(new_content)
|
||||
print("Successfully updated door_visit_history")
|
||||
else:
|
||||
print("Could not find function to replace")
|
||||
156
door_visit_patch.py
Normal file
156
door_visit_patch.py
Normal file
@ -0,0 +1,156 @@
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
django.setup()
|
||||
|
||||
import re
|
||||
from core import views
|
||||
|
||||
def patch_log_door_visit():
|
||||
with open('core/views.py', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
new_view = """def log_door_visit(request):
|
||||
\"\"\"
|
||||
Mark all targeted voters at a specific address as visited, update their flags,
|
||||
and create interaction records.
|
||||
Can also render a standalone page for logging a visit.
|
||||
\"\"\"
|
||||
selected_tenant_id = request.session.get("tenant_id")
|
||||
if not selected_tenant_id:
|
||||
return redirect("index")
|
||||
|
||||
tenant = get_object_or_404(Tenant, id=selected_tenant_id)
|
||||
campaign_settings, _ = CampaignSettings.objects.get_or_create(tenant=tenant)
|
||||
|
||||
# Capture query string for redirecting back with filters
|
||||
next_qs = request.POST.get("next_query_string", request.GET.get("next_query_string", ""))
|
||||
redirect_url = reverse("door_visits")
|
||||
if next_qs:
|
||||
redirect_url += f"?{next_qs}"
|
||||
|
||||
# Get address components from POST or GET
|
||||
address_street = request.POST.get("address_street", request.GET.get("address_street"))
|
||||
city = request.POST.get("city", request.GET.get("city"))
|
||||
state = request.POST.get("state", request.GET.get("state"))
|
||||
zip_code = request.POST.get("zip_code", request.GET.get("zip_code"))
|
||||
|
||||
if not address_street:
|
||||
messages.warning(request, "No address provided.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
# Find targeted voters at this exact address
|
||||
voters = Voter.objects.filter(
|
||||
tenant=tenant,
|
||||
address_street=address_street,
|
||||
city=city,
|
||||
state=state,
|
||||
zip_code=zip_code,
|
||||
is_targeted=True
|
||||
)
|
||||
|
||||
if not voters.exists() and request.method == "POST":
|
||||
messages.warning(request, f"No targeted voters found at {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
|
||||
voter_choices = [(v.id, f"{v.first_name} {v.last_name}") for v in voters]
|
||||
|
||||
# Get the volunteer linked to the current user
|
||||
volunteer = Volunteer.objects.filter(user=request.user, tenant=tenant).first()
|
||||
|
||||
if request.method == "POST":
|
||||
form = DoorVisitLogForm(request.POST, voter_choices=voter_choices)
|
||||
if form.is_valid():
|
||||
outcome = form.cleaned_data["outcome"]
|
||||
notes = form.cleaned_data["notes"]
|
||||
wants_yard_sign = form.cleaned_data["wants_yard_sign"]
|
||||
candidate_support = form.cleaned_data["candidate_support"]
|
||||
follow_up = form.cleaned_data["follow_up"]
|
||||
follow_up_voter_id = form.cleaned_data.get("follow_up_voter")
|
||||
call_notes = form.cleaned_data["call_notes"]
|
||||
|
||||
# Determine date/time in campaign timezone
|
||||
campaign_tz_name = campaign_settings.timezone or "America/Chicago"
|
||||
try:
|
||||
tz = zoneinfo.ZoneInfo(campaign_tz_name)
|
||||
except:
|
||||
tz = zoneinfo.ZoneInfo("America/Chicago")
|
||||
|
||||
interaction_date = timezone.now().astimezone(tz)
|
||||
|
||||
# Get or create InteractionType
|
||||
interaction_type, _ = InteractionType.objects.get_or_create(tenant=tenant, name="Door Visit")
|
||||
|
||||
# Get default caller for follow-ups
|
||||
default_caller = None
|
||||
if follow_up:
|
||||
default_caller = Volunteer.objects.filter(tenant=tenant, is_default_caller=True).first()
|
||||
|
||||
for voter in voters:
|
||||
# 1) Update voter flags
|
||||
voter.door_visit = True
|
||||
|
||||
# 2) If "Wants a Yard Sign" checkbox is selected
|
||||
if wants_yard_sign:
|
||||
voter.yard_sign = "wants"
|
||||
|
||||
# 3) Update support status if Supporting or Not Supporting
|
||||
if candidate_support in ["supporting", "not_supporting"]:
|
||||
voter.candidate_support = candidate_support
|
||||
|
||||
voter.save()
|
||||
|
||||
# 4) Create interaction
|
||||
Interaction.objects.create(
|
||||
voter=voter,
|
||||
volunteer=volunteer,
|
||||
type=interaction_type,
|
||||
date=interaction_date,
|
||||
description=outcome,
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# 5) Create ScheduledCall if follow_up is checked and this is the selected voter
|
||||
if follow_up and follow_up_voter_id and str(voter.id) == follow_up_voter_id:
|
||||
ScheduledCall.objects.create(
|
||||
tenant=tenant,
|
||||
voter=voter,
|
||||
volunteer=default_caller,
|
||||
comments=call_notes,
|
||||
status="pending"
|
||||
)
|
||||
|
||||
if follow_up:
|
||||
messages.success(request, f"Door visit logged and follow-up call scheduled for {address_street}.")
|
||||
else:
|
||||
messages.success(request, f"Door visit logged for {address_street}.")
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
messages.error(request, "There was an error in the visit log form.")
|
||||
return redirect(redirect_url)
|
||||
else:
|
||||
# GET request: render standalone page
|
||||
form = DoorVisitLogForm(voter_choices=voter_choices)
|
||||
context = {
|
||||
'selected_tenant': tenant,
|
||||
'visit_form': form,
|
||||
'address_street': address_street,
|
||||
'city': city,
|
||||
'state': state,
|
||||
'zip_code': zip_code,
|
||||
'voters': voters,
|
||||
'next_query_string': next_qs,
|
||||
}
|
||||
return render(request, 'core/log_door_visit.html', context)
|
||||
"""
|
||||
|
||||
# Replace the old function. We use regex to find the start and then match indentation for the end.
|
||||
pattern = r'def log_door_visit\(request\):.*?return redirect\(redirect_url\)'
|
||||
content = re.sub(pattern, new_view, content, flags=re.DOTALL)
|
||||
|
||||
with open('core/views.py', 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
patch_log_door_visit()
|
||||
22
fix_views.py
Normal file
22
fix_views.py
Normal file
@ -0,0 +1,22 @@
|
||||
import os
|
||||
|
||||
with open('core/views.py', 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
new_lines = []
|
||||
skip = False
|
||||
for i, line in enumerate(lines):
|
||||
if "return render(request, 'core/log_door_visit.html', context)" in line:
|
||||
new_lines.append(line)
|
||||
# Look ahead to see if there is duplicated code
|
||||
if i + 3 < len(lines) and "default_caller = None" in lines[i+3]:
|
||||
skip = True
|
||||
continue
|
||||
if skip:
|
||||
if "return redirect(redirect_url)" in line:
|
||||
skip = False
|
||||
continue
|
||||
new_lines.append(line)
|
||||
|
||||
with open('core/views.py', 'w') as f:
|
||||
f.writelines(new_lines)
|
||||
13
fix_views_v2.py
Normal file
13
fix_views_v2.py
Normal file
@ -0,0 +1,13 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
with open('core/views.py', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find the first occurrence of return render(request, 'core/log_door_visit.html', context)
|
||||
# and the next def door_visit_history(request):
|
||||
pattern = r"(return render\(request, 'core/log_door_visit.html', context\)).*?(def door_visit_history\(request\):)"
|
||||
new_content = re.sub(pattern, r"\1\n\n\2", content, flags=re.DOTALL)
|
||||
|
||||
with open('core/views.py', 'w') as f:
|
||||
f.write(new_content)
|
||||
33
patch_log_visit.py
Normal file
33
patch_log_visit.py
Normal file
@ -0,0 +1,33 @@
|
||||
import os
|
||||
|
||||
with open('core/views.py', 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
start_line = -1
|
||||
end_line = -1
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if 'def log_door_visit(request):' in line:
|
||||
start_line = i
|
||||
if start_line != -1 and 'def door_visit_history(request):' in line:
|
||||
end_line = i
|
||||
break
|
||||
|
||||
if start_line != -1 and end_line != -1:
|
||||
with open('core_views_log_visit.py', 'r') as f:
|
||||
new_content = f.read()
|
||||
|
||||
# Ensure there is a newline before door_visit_history
|
||||
if not new_content.endswith('\n'):
|
||||
new_content += '\n'
|
||||
|
||||
# Prepend indentation to new_content if needed, but it should be top-level def
|
||||
|
||||
lines[start_line:end_line] = [new_content + '\n']
|
||||
|
||||
with open('core/views.py', 'w') as f:
|
||||
f.writelines(lines)
|
||||
print("Successfully patched log_door_visit in core/views.py")
|
||||
else:
|
||||
print(f"Could not find log_door_visit boundaries: {start_line}, {end_line}")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user