347 lines
13 KiB
Python
347 lines
13 KiB
Python
import csv
|
|
from datetime import date
|
|
|
|
from django.contrib.admin import DateFieldListFilter, BooleanFieldListFilter
|
|
from django.forms import widgets
|
|
from django.contrib import admin
|
|
from django.http import HttpResponse, HttpResponseRedirect
|
|
from django.shortcuts import render
|
|
from django.urls import reverse
|
|
from django.utils.html import format_html
|
|
from django.utils.http import urlencode
|
|
from django.utils.translation import gettext_lazy
|
|
from import_export.admin import ImportExportModelAdmin
|
|
|
|
from .adminClone import CloneModelAdmin
|
|
from .context_helpers import bus_roll_context, emergency_contacts_context, traveller_roll_context, \
|
|
traveller_route_context, school_roll_context
|
|
from .email_helpers import email_companies_bus_roll, render_to_pdf, email_school_roll
|
|
from .form import SMSForm
|
|
from .models import *
|
|
|
|
|
|
class BusRollMixin:
|
|
|
|
def show_bus_roll(self, request, queryset):
|
|
return render_to_pdf('reports/bus_roll.html', bus_roll_context(queryset))
|
|
|
|
def show_emergency_contacts(self, request, queryset):
|
|
return render_to_pdf('reports/emergency_contacts.html', emergency_contacts_context(queryset))
|
|
|
|
def email_company(self, request, queryset):
|
|
return email_companies_bus_roll(request, queryset)
|
|
|
|
email_company.short_description = "Email Bus Roll to Company"
|
|
|
|
|
|
class ShuttleRollMixin:
|
|
|
|
def show_shuttle_roll(self, request, queryset):
|
|
if queryset is None:
|
|
buses = None
|
|
else:
|
|
buses = []
|
|
for shuttle in queryset:
|
|
if shuttle.bus not in buses:
|
|
buses.append(shuttle.bus)
|
|
return render_to_pdf('reports/bus_roll.html', bus_roll_context(buses, include_bus_stops=False))
|
|
|
|
|
|
class SchoolRollMixin:
|
|
|
|
def email_travellers_to_school(self, request, queryset):
|
|
return email_school_roll(request, queryset)
|
|
|
|
def show_school_travellers(self, request, queryset):
|
|
return render_to_pdf('reports/school_roll.html', school_roll_context(queryset))
|
|
|
|
def export_travellers_to_csv(self, request, queryset):
|
|
traveller_list = []
|
|
for school in queryset:
|
|
for travellerRoute in TravellerRoute.objects.filter(traveller__school=school):
|
|
if not travellerRoute.traveller.is_active():
|
|
continue
|
|
traveller_list.append(traveller_route_context(travellerRoute))
|
|
|
|
response = HttpResponse(content_type="text/csv")
|
|
response["Content-Disposition"] = f"attachment; filename=traveller_list_{date.today()}.csv"
|
|
|
|
writer = csv.DictWriter(response, fieldnames=traveller_list[0].keys())
|
|
writer.writeheader()
|
|
writer.writerows(traveller_list)
|
|
|
|
return response
|
|
|
|
|
|
class TravellerRollMixin:
|
|
|
|
def send_sms(self, request, queryset):
|
|
if 'send' in request.POST:
|
|
message = request.POST["message"]
|
|
send_to_parents = False
|
|
if request.POST.get("send_to_parents"):
|
|
send_to_parents = True
|
|
send_to_emergency_contacts = False
|
|
if request.POST.get("send_to_emergency_contacts"):
|
|
send_to_emergency_contacts = True
|
|
only_include_active_travellers = False
|
|
if request.POST.get("only_include_active_travellers"):
|
|
only_include_active_travellers = True
|
|
total = 0
|
|
for traveller in queryset:
|
|
if only_include_active_travellers and not traveller.is_active():
|
|
continue
|
|
total += traveller.send_sms(message, parents=send_to_parents, emergency=send_to_emergency_contacts)
|
|
self.message_user(request, f"SMS has been sent to {total} recipients")
|
|
return HttpResponseRedirect(request.get_full_path())
|
|
|
|
form = SMSForm(initial={'_selected_action': queryset.values_list('id', flat=True)})
|
|
|
|
return render(request, 'admin/sms_form.html', context={'form': form, 'items': queryset})
|
|
|
|
def export_to_csv(self, request, queryset):
|
|
traveller_list = traveller_roll_context(queryset)
|
|
response = HttpResponse(content_type="text/csv")
|
|
response["Content-Disposition"] = f"attachment; filename=traveller_list_{date.today()}.csv"
|
|
|
|
writer = csv.DictWriter(response, fieldnames=traveller_list[0].keys())
|
|
writer.writeheader()
|
|
writer.writerows(traveller_list)
|
|
|
|
return response
|
|
|
|
|
|
class ArchiveFilter(BooleanFieldListFilter):
|
|
parameter_name = 'is_archived'
|
|
|
|
def choices(self, changelist):
|
|
field_choices = dict(self.field.flatchoices)
|
|
for lookup, title in (
|
|
(None, gettext_lazy("All")),
|
|
("0", field_choices.get(True, gettext_lazy("Current"))),
|
|
("1", field_choices.get(False, gettext_lazy("Archived"))),
|
|
):
|
|
yield {
|
|
"selected": self.lookup_val == lookup and not self.lookup_val2,
|
|
"query_string": changelist.get_query_string(
|
|
{self.lookup_kwarg: lookup}, [self.lookup_kwarg2]
|
|
),
|
|
"display": title,
|
|
}
|
|
|
|
|
|
class HiddenNowTime(widgets.TimeInput):
|
|
pass
|
|
|
|
|
|
class MyImportExportModelAdmin(ImportExportModelAdmin):
|
|
def has_import_permission(self, request):
|
|
return request.user.is_superuser
|
|
|
|
|
|
@admin.register(Company)
|
|
class CompanyAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
|
|
list_display = ["name", "contact_name", "email", "buses"]
|
|
|
|
def buses(self, obj):
|
|
count = obj.bus_set.count()
|
|
url = (
|
|
reverse("admin:coord_bus_changelist")
|
|
+ "?"
|
|
+ urlencode({"company__id__exact": f"{obj.id}"})
|
|
)
|
|
return format_html('<a href="{}">{} Buses</a>', url, count)
|
|
|
|
def email(self, obj):
|
|
return format_html('<a href="mailto:{}">{}</a>', obj.contact_email, obj.contact_email)
|
|
|
|
|
|
class DriverInline(admin.StackedInline):
|
|
model = Driver
|
|
extra = 0
|
|
|
|
|
|
class BusStopInline(admin.TabularInline):
|
|
model = BusStop
|
|
extra = 0
|
|
ordering = ("am_time",)
|
|
|
|
|
|
@admin.register(Bus)
|
|
class BusesAdmin(MyImportExportModelAdmin, admin.ModelAdmin, BusRollMixin):
|
|
list_filter = ["company"]
|
|
list_display = ["route_name", "company", "contract_number", "seating_capacity", "route_travellers"]
|
|
readonly_fields = ["traveller_count"]
|
|
actions = ["email_company", "show_bus_roll", "show_emergency_contacts"]
|
|
inlines = [DriverInline, BusStopInline]
|
|
fieldsets = [
|
|
(None, {'fields': [
|
|
"company", "route_name", "contract_number", "registration",
|
|
"traveller_count", "seating_capacity", "make", "model", "notes"
|
|
]})
|
|
]
|
|
|
|
def route_travellers(self, obj):
|
|
url = (
|
|
reverse("admin:coord_traveller_changelist")
|
|
+ "?"
|
|
+ urlencode({"bus_stops__bus__id__exact": f"{obj.id}"})
|
|
)
|
|
return format_html('<a href="{}">{} Travellers</a>', url, obj.traveller_count())
|
|
|
|
|
|
@admin.register(BusStop)
|
|
class BusStopAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
|
|
list_filter = ["bus__company", "bus__route_name"]
|
|
list_display = ["__str__", "am_time", "pm_time", "address"]
|
|
search_fields = ["bus__route_name", "address"]
|
|
|
|
|
|
@admin.register(Suburb)
|
|
class SuburbsAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
|
|
list_filter = ["state"]
|
|
|
|
|
|
class TravellerRouteInline(admin.TabularInline):
|
|
model = TravellerRoute
|
|
extra = 0
|
|
|
|
|
|
@admin.register(Traveller)
|
|
class TravellerAdmin(MyImportExportModelAdmin, CloneModelAdmin, admin.ModelAdmin, TravellerRollMixin):
|
|
list_display = ["first_name", "last_name", "school", "year_level", "residential_address", "residential_suburb", "stop_route", "shuttle", "travel_start_date", "travel_end_date"]
|
|
list_filter = ["school", "year_level", "eligibility_status", "bus_stops__bus", "shuttle", "residential_suburb", ("travel_end_date", DateFieldListFilter), ("is_archived", ArchiveFilter)]
|
|
search_fields = ["first_name", "last_name", "residential_address"]
|
|
cloneable_fields = ["last_name", "residential_address", "residential_suburb",
|
|
"postal_address", "postal_suburb", "eligibility_status", "shuttle",
|
|
"parent_A_firstname", "parent_A_lastname", "parent_A_phone", "parent_A_email",
|
|
"parent_B_firstname", "parent_B_lastname", "parent_B_phone", "parent_B_email",
|
|
"emergency_contact_A_firstname", "emergency_contact_A_lastname", "emergency_contact_A_phone",
|
|
"emergency_contact_A_relation", "emergency_contact_B_firstname", "emergency_contact_B_lastname",
|
|
"emergency_contact_B_phone", "emergency_contact_B_relation"]
|
|
inlines = [TravellerRouteInline]
|
|
readonly_fields = ["fare_paying", "created_on", "last_edit", "is_archived"]
|
|
actions = ["export_to_csv", "send_sms", "yearly_rollover"]
|
|
fieldsets = [
|
|
(None, {
|
|
'fields': [
|
|
"school",
|
|
"first_name",
|
|
"last_name",
|
|
"dob",
|
|
"year_level",
|
|
]
|
|
}),
|
|
('Address', {
|
|
'classes': ('collapse',),
|
|
'fields': [
|
|
"residential_address",
|
|
"residential_suburb",
|
|
"postal_address",
|
|
"postal_suburb",
|
|
]
|
|
}),
|
|
('Office Use', {
|
|
'classes': ('collapse',),
|
|
'fields': [
|
|
"distance_to_school",
|
|
"travel_start_date",
|
|
"travel_end_date",
|
|
"eligibility_status",
|
|
"fare_paying",
|
|
"term_1_paid",
|
|
"term_2_paid",
|
|
"term_3_paid",
|
|
"term_4_paid",
|
|
"assessment_date",
|
|
"application_form_completed",
|
|
"parent_notified",
|
|
"seat_number",
|
|
"created_on",
|
|
"last_edit",
|
|
"is_archived",
|
|
]
|
|
}),
|
|
('Adult Contacts', {
|
|
'classes': ('collapse',),
|
|
'fields': [
|
|
"parent_A_firstname",
|
|
"parent_A_lastname",
|
|
"parent_A_phone",
|
|
"parent_A_email",
|
|
"parent_B_firstname",
|
|
"parent_B_lastname",
|
|
"parent_B_phone",
|
|
"parent_B_email",
|
|
"emergency_contact_A_firstname",
|
|
"emergency_contact_A_lastname",
|
|
"emergency_contact_A_phone",
|
|
"emergency_contact_A_relation",
|
|
"emergency_contact_B_firstname",
|
|
"emergency_contact_B_lastname",
|
|
"emergency_contact_B_phone",
|
|
"emergency_contact_B_relation"
|
|
]
|
|
}),
|
|
(None, {'fields': ["notes", "shuttle"]})
|
|
]
|
|
# list_display_links = None
|
|
|
|
def yearly_rollover(self, request, queryset):
|
|
pass
|
|
|
|
def stop_route(self, obj):
|
|
stops = BusStop.objects.filter(traveller__id__exact=obj.id)
|
|
if stops.count() == 0:
|
|
return ""
|
|
if stops.count() == 1:
|
|
return stops.first()
|
|
return "Multiple"
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if obj.is_archived and obj.travel_end_date is None:
|
|
obj.is_archived = False
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(TravellerRoute)
|
|
class TravellerRouteAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
|
|
list_display = ["traveller", "busStop"]
|
|
|
|
|
|
@admin.register(School)
|
|
class SchoolAdmin(MyImportExportModelAdmin, admin.ModelAdmin, SchoolRollMixin):
|
|
list_display = ["__str__", "address", "suburb", "school_email", "phone"]
|
|
actions = ["email_travellers_to_school", "show_school_travellers", "export_travellers_to_csv"]
|
|
|
|
def school_email(self, obj):
|
|
return format_html('<a href="mailto:{}">{}</a>', obj.email, obj.email)
|
|
|
|
|
|
@admin.register(Setting)
|
|
class SettingAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
|
|
list_display = ["name", "value"]
|
|
|
|
|
|
@admin.register(Shuttle)
|
|
class ShuttleAdmin(MyImportExportModelAdmin, admin.ModelAdmin, ShuttleRollMixin):
|
|
list_display = ["__str__", "school", "bus", "shuttle_travellers"]
|
|
actions = ["show_shuttle_roll"]
|
|
|
|
def shuttle_travellers(self, obj):
|
|
url = (
|
|
reverse("admin:coord_traveller_changelist")
|
|
+ "?"
|
|
+ urlencode({"shuttle__id__exact": f"{obj.id}"})
|
|
)
|
|
return format_html('<a href="{}">{} Travellers</a>', url, obj.traveller_count())
|
|
|
|
|
|
@admin.register(Driver)
|
|
class DriverAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
|
|
list_display = ["__str__", "route", "phone_number"]
|
|
|
|
def route(self, obj):
|
|
url = reverse("admin:coord_bus_change", args=(obj.bus.id,))
|
|
return format_html('<a href="{}">{}</a>', url, obj.bus)
|