Created new models to replicate out of and split coord app
Moved helpers and views to their respective new apps
This commit is contained in:
@@ -1,3 +1,79 @@
|
||||
from django.contrib import admin
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.utils.http import urlencode
|
||||
|
||||
# Register your models here.
|
||||
from common.admin import MyImportExportModelAdmin
|
||||
from transport.admin_mixins import BusRollMixin, ShuttleRollMixin
|
||||
from transport.models import Driver, BusStop, Company, Bus, Shuttle
|
||||
|
||||
|
||||
@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 = ["show_bus_roll", "show_bus_roll_on_date", "show_emergency_contacts", "sms_traveller_contacts", "email_bus_roll", "email_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(Shuttle)
|
||||
class ShuttleAdmin(MyImportExportModelAdmin, admin.ModelAdmin, ShuttleRollMixin):
|
||||
list_display = ["__str__", "school", "bus", "shuttle_travellers"]
|
||||
actions = ["show_shuttle_roll", "email_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())
|
||||
@@ -0,0 +1,54 @@
|
||||
from common.documents import render_to_pdf
|
||||
from messaging.services.email import email_companies_bus_roll, email_companies_emergency_contacts, \
|
||||
email_school_shuttle_roll
|
||||
from messaging.services.sms import send_sms
|
||||
from transport.context_busroll import bus_roll_context
|
||||
from transport.context_helpers import emergency_contacts_context
|
||||
from transport.forms import roll_date_selector
|
||||
from traveller.models import Traveller
|
||||
|
||||
|
||||
class BusRollMixin:
|
||||
|
||||
def show_bus_roll(self, request, queryset):
|
||||
return render_to_pdf('reports/bus_roll.html', bus_roll_context(queryset))
|
||||
|
||||
def show_bus_roll_on_date(self, request, queryset):
|
||||
return roll_date_selector(self, request, queryset)
|
||||
|
||||
def show_emergency_contacts(self, request, queryset):
|
||||
return render_to_pdf('reports/emergency_contacts.html', emergency_contacts_context(queryset))
|
||||
|
||||
def sms_traveller_contacts(self, request, queryset):
|
||||
travellers = None
|
||||
for bus in queryset:
|
||||
query = Traveller.objects.filter(bus_stops__bus=bus).filter(is_active=True).distinct()
|
||||
if travellers is None:
|
||||
travellers = query
|
||||
else:
|
||||
travellers.union(query)
|
||||
return send_sms(self, request, travellers)
|
||||
|
||||
def email_bus_roll(self, request, queryset):
|
||||
return email_companies_bus_roll(request, queryset)
|
||||
|
||||
def email_emergency_contacts(self, request, queryset):
|
||||
return email_companies_emergency_contacts(request, queryset)
|
||||
|
||||
email_bus_roll.short_description = "Email Bus Roll to Company"
|
||||
email_emergency_contacts.short_description = "Email Emergency Contacts 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))
|
||||
|
||||
def email_shuttle_roll(self, request, queryset):
|
||||
return email_school_shuttle_roll(request, queryset)
|
||||
@@ -0,0 +1,73 @@
|
||||
import datetime
|
||||
|
||||
from transport.models import Bus, Shuttle, BusStop
|
||||
from traveller.models import TravellerRoute, Traveller
|
||||
|
||||
|
||||
def route_paged_context(bus, date=None):
|
||||
table_header_size = 5
|
||||
page_max_size = 45
|
||||
page_size = 3 # Account for traveller numbers at the top of the first page
|
||||
route_stops = []
|
||||
for bus_stop in BusStop.objects.filter(bus=bus):
|
||||
traveller_routes = TravellerRoute.objects.filter(busStop=bus_stop)
|
||||
traveller_list = []
|
||||
for trav_route in traveller_routes:
|
||||
traveller = trav_route.traveller
|
||||
if not traveller._is_active(date):
|
||||
continue
|
||||
is_fared = "---"
|
||||
if traveller.eligibility_status == "2":
|
||||
is_fared = "Y"
|
||||
traveller_list.append({
|
||||
'display': f"{traveller} ({traveller.get_year_level_display()}, {traveller.school.shortName})",
|
||||
'isFared': is_fared
|
||||
})
|
||||
|
||||
stop_size = len(traveller_list)
|
||||
page_break = False
|
||||
page_size += table_header_size + stop_size
|
||||
if page_size > page_max_size:
|
||||
if len(route_stops) > 0: # Don't break the page if it's the first stop
|
||||
page_break = True
|
||||
page_size = table_header_size + stop_size
|
||||
|
||||
route_stops.append({
|
||||
'stop_num': bus_stop.get_stop_number(),
|
||||
'name': bus_stop.address,
|
||||
'am': bus_stop.am_time,
|
||||
'pm': bus_stop.pm_time,
|
||||
'travellers': traveller_list,
|
||||
'page_break': page_break
|
||||
})
|
||||
return route_stops
|
||||
|
||||
def shuttle_route_context(shuttle, date=None):
|
||||
shuttle_travellers = []
|
||||
for traveller in Traveller.objects.filter(shuttle=shuttle):
|
||||
if traveller._is_active(date):
|
||||
shuttle_travellers.append({
|
||||
'display': f"{traveller} ({traveller.get_year_level_display()}, {traveller.school})",
|
||||
})
|
||||
return {'shuttle': shuttle, 'shuttle_travellers': shuttle_travellers, 'traveller_count': shuttle.traveller_count(date)}
|
||||
|
||||
def bus_roll_context(queryset=None, include_bus_stops=True, date=None):
|
||||
bus_routes = []
|
||||
if queryset is None:
|
||||
buses = Bus.objects.all()
|
||||
else:
|
||||
buses = queryset
|
||||
|
||||
for bus in buses:
|
||||
route_stops = []
|
||||
if include_bus_stops:
|
||||
route_stops = route_paged_context(bus=bus, date=date)
|
||||
|
||||
shuttle_routes = []
|
||||
for shuttle in Shuttle.objects.filter(bus=bus):
|
||||
shuttle_routes.append(shuttle_route_context(shuttle, date))
|
||||
|
||||
bus_routes.append({'name': bus.route_name, 'traveller_count': bus.traveller_count(date), 'seating_capacity': bus.seating_capacity, 'bus': bus, 'route_stops': route_stops, 'shuttle_routes': shuttle_routes})
|
||||
if date is None:
|
||||
date = datetime.date.today()
|
||||
return {'routes': bus_routes, 'date': date.strftime('%Y-%m-%d')}
|
||||
@@ -0,0 +1,88 @@
|
||||
from transport.models import Bus, Driver, BusStop, Shuttle
|
||||
from traveller.models import TravellerRoute, Traveller
|
||||
|
||||
|
||||
def bus_summary_context():
|
||||
bus_routes = []
|
||||
for bus in Bus.objects.all():
|
||||
|
||||
drivers = []
|
||||
for driver in Driver.objects.filter(bus=bus):
|
||||
drivers.append(driver)
|
||||
|
||||
stops = []
|
||||
for bus_stop in BusStop.objects.filter(bus=bus):
|
||||
stops.append(bus_stop)
|
||||
|
||||
traveller_count = 0
|
||||
for travellerRoute in TravellerRoute.objects.filter(busStop__bus=bus):
|
||||
if travellerRoute.traveller._is_active():
|
||||
traveller_count += 1
|
||||
|
||||
shuttle_name = ""
|
||||
shuttle_count = 0
|
||||
for shuttle in Shuttle.objects.filter(bus=bus):
|
||||
if shuttle_name == "":
|
||||
shuttle_name = shuttle.school.shortName
|
||||
else:
|
||||
shuttle_name += f", {shuttle.school.shortName}"
|
||||
|
||||
for traveller in Traveller.objects.filter(shuttle=shuttle):
|
||||
if traveller._is_active():
|
||||
shuttle_count += 1
|
||||
|
||||
over_capacity = traveller_count > bus.seating_capacity or shuttle_count > bus.seating_capacity
|
||||
if shuttle_count == 0:
|
||||
shuttle_count = ""
|
||||
|
||||
bus_routes.append({
|
||||
'bus': bus,
|
||||
'drivers': drivers,
|
||||
'stops': stops,
|
||||
'traveller_count': traveller_count,
|
||||
'shuttle_name': shuttle_name,
|
||||
'shuttle_count': shuttle_count,
|
||||
'over_capacity': over_capacity,
|
||||
})
|
||||
return {'routes': bus_routes}
|
||||
|
||||
def emergency_contacts_context(queryset=None):
|
||||
if queryset is None:
|
||||
buses = Bus.objects.all()
|
||||
else:
|
||||
buses = queryset
|
||||
|
||||
bus_routes = []
|
||||
for bus in buses:
|
||||
drivers = []
|
||||
for driver in Driver.objects.filter(bus=bus):
|
||||
drivers.append(driver)
|
||||
traveller_list = []
|
||||
for travellerRoute in TravellerRoute.objects.filter(busStop__bus=bus):
|
||||
traveller = travellerRoute.traveller
|
||||
if not traveller._is_active():
|
||||
continue
|
||||
for family in traveller.get_families():
|
||||
parent_a = ""
|
||||
if family.parent_A_firstname:
|
||||
parent_a = f"{family.parent_A_firstname} {family.parent_A_lastname} ({family.parent_A_phone})"
|
||||
parent_b = ""
|
||||
if family.parent_B_firstname:
|
||||
parent_b = f"{family.parent_B_firstname} {family.parent_B_lastname} ({family.parent_B_phone})"
|
||||
contact_a = ""
|
||||
if family.emergency_contact_A_firstname:
|
||||
contact_a = f"{family.emergency_contact_A_firstname} {family.emergency_contact_A_lastname} ({family.emergency_contact_A_phone})"
|
||||
contact_b = ""
|
||||
if family.emergency_contact_B_firstname:
|
||||
contact_b = f"{family.emergency_contact_B_firstname} {family.emergency_contact_B_lastname} ({family.emergency_contact_B_phone})"
|
||||
traveller_list.append({
|
||||
'traveller': traveller,
|
||||
'parent_a': parent_a,
|
||||
'parent_b': parent_b,
|
||||
'contact_a': contact_a,
|
||||
'contact_b': contact_b,
|
||||
'note': travellerRoute.notes
|
||||
})
|
||||
bus_routes.append({'bus': bus, 'drivers': drivers, 'travellers': traveller_list})
|
||||
|
||||
return {'routes': bus_routes}
|
||||
@@ -0,0 +1,23 @@
|
||||
from datetime import datetime
|
||||
|
||||
from django import forms
|
||||
from django.shortcuts import render
|
||||
|
||||
from common.documents import render_to_pdf
|
||||
from transport.context_busroll import bus_roll_context
|
||||
|
||||
|
||||
class RollDateSelector(forms.Form):
|
||||
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
|
||||
|
||||
|
||||
def roll_date_selector(mixin, request, queryset):
|
||||
if 'generate' in request.POST:
|
||||
date = request.POST.get("date")
|
||||
if date:
|
||||
date = datetime.strptime(date, '%Y-%m-%d')
|
||||
else:
|
||||
date = None
|
||||
return render_to_pdf('reports/bus_roll.html', bus_roll_context(queryset, date=date))
|
||||
form = RollDateSelector(initial={'_selected_action': queryset.values_list('id', flat=True)})
|
||||
return render(request, 'admin/date_selector.html', context={'form': form})
|
||||
@@ -1,3 +1,104 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from common.models import Suburb
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
contact_name = models.CharField(max_length=50, blank=True)
|
||||
contact_number = models.CharField(max_length=15, blank=True)
|
||||
contact_mobile = models.CharField(max_length=15, blank=True)
|
||||
contact_email = models.CharField(max_length=50, blank=True)
|
||||
address = models.CharField(max_length=50, blank=True)
|
||||
suburb = models.ForeignKey(Suburb, on_delete=models.CASCADE)
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Companies"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
class Bus(models.Model):
|
||||
company = models.ForeignKey(Company, on_delete=models.CASCADE)
|
||||
route_name = models.CharField(max_length=50, unique=True)
|
||||
contract_number = models.CharField(max_length=20, blank=True)
|
||||
registration = models.CharField(max_length=10, blank=True)
|
||||
seating_capacity = models.SmallIntegerField()
|
||||
make = models.CharField(max_length=15, blank=True)
|
||||
model = models.CharField(max_length=15, blank=True)
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Buses"
|
||||
ordering = ["route_name"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.route_name}"
|
||||
|
||||
def traveller_count(self, date=None):
|
||||
count = 0
|
||||
from traveller.models import Traveller
|
||||
for traveller in Traveller.objects.filter(bus_stops__bus=self):
|
||||
if traveller._is_active(date):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
class Shuttle(models.Model):
|
||||
bus = models.ForeignKey(Bus, on_delete=models.CASCADE)
|
||||
school = models.ForeignKey("traveller.School", on_delete=models.CASCADE)
|
||||
custom_name = models.CharField(max_length=10, blank=True)
|
||||
# transfer_school = models.ForeignKey(School, related_name='transfer_school', on_delete=models.CASCADE)
|
||||
am_service = models.BooleanField(default=True)
|
||||
pm_service = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["school__name"]
|
||||
|
||||
def __str__(self):
|
||||
custom_name = self.custom_name
|
||||
if custom_name:
|
||||
custom_name = f" ({self.custom_name})"
|
||||
else:
|
||||
custom_name = ""
|
||||
return f"{self.school.shortName} <-> {self.bus.route_name}{custom_name}"
|
||||
|
||||
def traveller_count(self, date=None):
|
||||
count = 0
|
||||
from traveller.models import Traveller
|
||||
for traveller in Traveller.objects.filter(shuttle=self):
|
||||
if traveller._is_active(date):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
class Driver(models.Model):
|
||||
bus = models.ForeignKey(Bus, on_delete=models.CASCADE)
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
phone_number = models.CharField(max_length=15, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["last_name"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
|
||||
class BusStop(models.Model):
|
||||
bus = models.ForeignKey(Bus, on_delete=models.CASCADE)
|
||||
am_time = models.TimeField()
|
||||
pm_time = models.TimeField()
|
||||
address = models.CharField(max_length=100)
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["bus__route_name", "am_time"]
|
||||
|
||||
def get_stop_number(self):
|
||||
return BusStop.objects.filter(bus=self.bus, am_time__lt=self.am_time).count() + 1
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.bus.route_name} #{self.get_stop_number()} - {self.address}"
|
||||
@@ -1,3 +1,20 @@
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
from common.documents import render_to_pdf
|
||||
from transport.context_busroll import bus_roll_context
|
||||
from transport.context_helpers import bus_summary_context, emergency_contacts_context
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def bus_summary(request):
|
||||
return render(request, 'reports/bus_summary.html', bus_summary_context())
|
||||
|
||||
@staff_member_required
|
||||
def emergency_contacts(request):
|
||||
return render_to_pdf('reports/emergency_contacts.html', emergency_contacts_context())
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def bus_roll(request):
|
||||
return render_to_pdf('reports/bus_roll.html', bus_roll_context())
|
||||
Reference in New Issue
Block a user