diff --git a/busManager/busManager/settings.py b/busManager/busManager/settings.py
index 010d87c..48d6c09 100644
--- a/busManager/busManager/settings.py
+++ b/busManager/busManager/settings.py
@@ -45,6 +45,7 @@ INSTALLED_APPS = [
'import_export',
'rangefilter',
'common',
+ 'locations',
'transport',
'traveller',
'messaging'
diff --git a/busManager/locations/__init__.py b/busManager/locations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/busManager/locations/admin.py b/busManager/locations/admin.py
new file mode 100644
index 0000000..23d948f
--- /dev/null
+++ b/busManager/locations/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from common.admin import MyImportExportModelAdmin
+from locations.models import Suburb, Location
+
+
+@admin.register(Suburb)
+class SuburbsAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
+ list_filter = ["state"]
+
+@admin.register(Location)
+class LocationAdmin(admin.ModelAdmin):
+ search_fields = ["address", "suburb__name", "suburb__postcode"]
\ No newline at end of file
diff --git a/busManager/locations/apps.py b/busManager/locations/apps.py
new file mode 100644
index 0000000..9e7ec9a
--- /dev/null
+++ b/busManager/locations/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class LocationsConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'locations'
diff --git a/busManager/locations/migrations/__init__.py b/busManager/locations/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/busManager/locations/models.py b/busManager/locations/models.py
new file mode 100644
index 0000000..4534ba6
--- /dev/null
+++ b/busManager/locations/models.py
@@ -0,0 +1,35 @@
+from django.db import models
+
+class Suburb(models.Model):
+ STATE = [
+ ("VIC", "Victoria"),
+ ("NSW", "New South Wales"),
+ ("SA", "South Australia"),
+ ("ACT", "Australia Capital Territory"),
+ ("QLD", "Queensland"),
+ ("NT", "Northern Territory"),
+ ("WA", "Western Australia"),
+ ("TAS", "Tasmania"),
+ ]
+ name = models.CharField(max_length=30, unique=True)
+ state = models.CharField(max_length=3, choices=STATE)
+ postcode = models.PositiveSmallIntegerField()
+ distance = models.PositiveSmallIntegerField(blank=True, null=True)
+
+ class Meta:
+ ordering = ["name"]
+
+ def __str__(self):
+ return f"{self.name}, {self.state} {self.postcode}"
+
+class Location(models.Model):
+ address = models.CharField(max_length=255, blank=True)
+ suburb = models.ForeignKey(Suburb, on_delete=models.PROTECT)
+
+ latitude = models.FloatField(null=True, blank=True)
+ longitude = models.FloatField(null=True, blank=True)
+
+ class Meta: unique_together = ("suburb", "address")
+
+ def __str__(self):
+ return f"{self.address}, {self.suburb}"
\ No newline at end of file
diff --git a/busManager/locations/tests.py b/busManager/locations/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/busManager/locations/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/busManager/locations/views.py b/busManager/locations/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/busManager/locations/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/busManager/messaging/services/sms.py b/busManager/messaging/services/sms.py
index 24e1aca..8d926cf 100644
--- a/busManager/messaging/services/sms.py
+++ b/busManager/messaging/services/sms.py
@@ -1,9 +1,12 @@
import requests
from django import forms
from django.conf import settings
+from django.db.models import Prefetch
from django.http import HttpResponseRedirect
from django.shortcuts import render
+from traveller.models import Family
+
def _get_token():
url = 'https://products.api.telstra.com/v2/oauth/token'
@@ -81,45 +84,120 @@ class SMSForm(forms.Form):
def _get_numbers(request, queryset):
+
send_to_parents = bool(request.POST.get("send_to_parents"))
send_to_emergency_contacts = bool(request.POST.get("send_to_emergency_contacts"))
only_include_active_travellers = bool(request.POST.get("only_include_active_travellers"))
- numbers = []
- for traveller in queryset:
- if only_include_active_travellers and not traveller._is_active():
+ numbers = set()
+
+ families_prefetch = Prefetch(
+ "family_set",
+ queryset=Family.objects.select_related(
+ "contact_A",
+ "contact_B",
+ "emergency_contact_A",
+ "emergency_contact_B",
+ )
+ )
+
+ travellers = queryset.prefetch_related(families_prefetch)
+
+ for traveller in travellers:
+
+ if only_include_active_travellers and not traveller.is_active:
continue
- for family in traveller.get_families():
- family_numbers, _ = family.get_parsed_numbers(parents=send_to_parents, emergency=send_to_emergency_contacts)
- if family_numbers:
- numbers = numbers + family_numbers
- return list(set(numbers)) # Remove duplicates
+
+ for family in traveller.family_set.all():
+
+ family_numbers, _ = family.get_parsed_numbers(
+ parents=send_to_parents,
+ emergency=send_to_emergency_contacts
+ )
+
+ numbers.update(family_numbers)
+
+ return list(numbers)
def _family_context(queryset):
+
+ families_prefetch = Prefetch(
+ "family_set",
+ queryset=Family.objects.select_related(
+ "contact_A",
+ "contact_B",
+ "emergency_contact_A",
+ "emergency_contact_B",
+ )
+ )
+
+ travellers = queryset.prefetch_related(families_prefetch)
+
family_set = []
- for traveller in queryset:
- families = traveller.get_families()
- if len(families) == 0:
+
+ for traveller in travellers:
+
+ families = traveller.family_set.all()
+
+ if not families:
family_set.append({
- 'traveller': traveller.__str__(),
- 'has_failed_number': True
+ "traveller": str(traveller),
+ "has_failed_number": True
})
+ continue
+
for family in families:
+
_, failed_numbers = family.get_parsed_numbers(True, True)
+
family_context = {
- 'traveller': traveller.__str__(),
- 'has_failed_number': len(failed_numbers) > 0
+ "traveller": str(traveller),
+ "has_failed_number": len(failed_numbers) > 0,
}
- if family.parent_A_phone:
- family_context['parent_A'] = f"{family.parent_A_firstname} {family.parent_A_lastname} ({family.parent_A_phone})"
- if family.parent_B_phone:
- family_context['parent_B'] = f"{family.parent_B_firstname} {family.parent_B_lastname} ({family.parent_B_phone})"
- if family.emergency_contact_A_phone:
- family_context['contact_A'] = f"{family.emergency_contact_A_firstname} {family.emergency_contact_A_lastname} ({family.emergency_contact_A_phone})"
- if family.emergency_contact_B_phone:
- family_context['contact_B'] = f"{family.emergency_contact_B_firstname} {family.emergency_contact_B_lastname} ({family.emergency_contact_B_phone})"
+
+ # -----------------------
+ # Parent A
+ # -----------------------
+ if family.contact_A:
+ family_context["parent_A"] = (
+ f"{family.contact_A.first_name} "
+ f"{family.contact_A.last_name} "
+ f"({family.contact_A.phone})"
+ )
+
+ # -----------------------
+ # Parent B
+ # -----------------------
+ if family.contact_B:
+ family_context["parent_B"] = (
+ f"{family.contact_B.first_name} "
+ f"{family.contact_B.last_name} "
+ f"({family.contact_B.phone})"
+ )
+
+ # -----------------------
+ # Emergency A
+ # -----------------------
+ if family.emergency_contact_A:
+ family_context["contact_A"] = (
+ f"{family.emergency_contact_A.first_name} "
+ f"{family.emergency_contact_A.last_name} "
+ f"({family.emergency_contact_A.phone})"
+ )
+
+ # -----------------------
+ # Emergency B
+ # -----------------------
+ if family.emergency_contact_B:
+ family_context["contact_B"] = (
+ f"{family.emergency_contact_B.first_name} "
+ f"{family.emergency_contact_B.last_name} "
+ f"({family.emergency_contact_B.phone})"
+ )
+
family_set.append(family_context)
+
return family_set
diff --git a/busManager/migration_scripts/copy_bus.py b/busManager/migration_scripts/Coord To Split Apps/copy_bus.py
similarity index 100%
rename from busManager/migration_scripts/copy_bus.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_bus.py
diff --git a/busManager/migration_scripts/copy_busstop.py b/busManager/migration_scripts/Coord To Split Apps/copy_busstop.py
similarity index 100%
rename from busManager/migration_scripts/copy_busstop.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_busstop.py
diff --git a/busManager/migration_scripts/copy_company.py b/busManager/migration_scripts/Coord To Split Apps/copy_company.py
similarity index 100%
rename from busManager/migration_scripts/copy_company.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_company.py
diff --git a/busManager/migration_scripts/copy_driver.py b/busManager/migration_scripts/Coord To Split Apps/copy_driver.py
similarity index 100%
rename from busManager/migration_scripts/copy_driver.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_driver.py
diff --git a/busManager/migration_scripts/copy_families.py b/busManager/migration_scripts/Coord To Split Apps/copy_families.py
similarity index 100%
rename from busManager/migration_scripts/copy_families.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_families.py
diff --git a/busManager/migration_scripts/copy_schools.py b/busManager/migration_scripts/Coord To Split Apps/copy_schools.py
similarity index 100%
rename from busManager/migration_scripts/copy_schools.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_schools.py
diff --git a/busManager/migration_scripts/copy_shuttle.py b/busManager/migration_scripts/Coord To Split Apps/copy_shuttle.py
similarity index 100%
rename from busManager/migration_scripts/copy_shuttle.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_shuttle.py
diff --git a/busManager/migration_scripts/Coord To Split Apps/copy_suburbs.py b/busManager/migration_scripts/Coord To Split Apps/copy_suburbs.py
new file mode 100644
index 0000000..e53e1ad
--- /dev/null
+++ b/busManager/migration_scripts/Coord To Split Apps/copy_suburbs.py
@@ -0,0 +1,26 @@
+from setup_django import *
+
+from coord.models import Suburb as OldSuburb
+from common.models import Suburb as NewSuburb
+
+created = 0
+skipped = 0
+
+for old in OldSuburb.objects.all():
+
+ obj, was_created = NewSuburb.objects.get_or_create(
+ id=old.id,
+ defaults={
+ "name": old.name,
+ "state": old.state,
+ "postcode": old.postcode,
+ "distance": old.distance,
+ }
+ )
+
+ if was_created:
+ created += 1
+ else:
+ skipped += 1
+
+print(f"Created: {created}, Skipped: {skipped}")
\ No newline at end of file
diff --git a/busManager/migration_scripts/copy_traveller_routes.py b/busManager/migration_scripts/Coord To Split Apps/copy_traveller_routes.py
similarity index 100%
rename from busManager/migration_scripts/copy_traveller_routes.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_traveller_routes.py
diff --git a/busManager/migration_scripts/copy_travellers.py b/busManager/migration_scripts/Coord To Split Apps/copy_travellers.py
similarity index 100%
rename from busManager/migration_scripts/copy_travellers.py
rename to busManager/migration_scripts/Coord To Split Apps/copy_travellers.py
diff --git a/busManager/migration_scripts/copy_suburbs.py b/busManager/migration_scripts/copy_suburbs.py
index e53e1ad..6faf99b 100644
--- a/busManager/migration_scripts/copy_suburbs.py
+++ b/busManager/migration_scripts/copy_suburbs.py
@@ -1,7 +1,7 @@
from setup_django import *
-from coord.models import Suburb as OldSuburb
-from common.models import Suburb as NewSuburb
+from common.models import Suburb as OldSuburb
+from locations.models import Suburb as NewSuburb
created = 0
skipped = 0
diff --git a/busManager/migration_scripts/populate_contacts.py b/busManager/migration_scripts/populate_contacts.py
new file mode 100644
index 0000000..e89ab6e
--- /dev/null
+++ b/busManager/migration_scripts/populate_contacts.py
@@ -0,0 +1,73 @@
+from setup_django import *
+
+from traveller.models import Family, ContactPerson
+
+created_contacts = 0
+updated_families = 0
+
+for family in Family.objects.all():
+ if family.parent_A_firstname or family.parent_A_lastname:
+
+ contact_A, created = ContactPerson.objects.get_or_create(
+ first_name=family.parent_A_firstname.strip() if family.parent_A_firstname else "",
+ last_name=family.parent_A_lastname.strip() if family.parent_A_lastname else "",
+ phone=family.parent_A_phone.strip() if family.parent_A_phone else "",
+ email=family.parent_A_email.strip() if family.parent_A_email else "",
+ )
+
+ family.contact_A = contact_A
+
+ if created:
+ created_contacts += 1
+
+ if family.parent_B_firstname or family.parent_B_lastname:
+
+ contact_B, created = ContactPerson.objects.get_or_create(
+ first_name=family.parent_B_firstname.strip() if family.parent_B_firstname else "",
+ last_name=family.parent_B_lastname.strip() if family.parent_B_lastname else "",
+ phone=family.parent_B_phone.strip() if family.parent_B_phone else "",
+ email=family.parent_B_email.strip() if family.parent_B_email else "",
+ )
+
+ family.contact_B = contact_B
+
+ if created:
+ created_contacts += 1
+
+ # -------------------------
+ # Emergency A
+ # -------------------------
+ if family.emergency_contact_A_firstname or family.emergency_contact_A_lastname:
+
+ contact_EA, created = ContactPerson.objects.get_or_create(
+ first_name=family.emergency_contact_A_firstname.strip() if family.emergency_contact_A_firstname else "",
+ last_name=family.emergency_contact_A_lastname.strip() if family.emergency_contact_A_lastname else "",
+ phone=family.emergency_contact_A_phone.strip() if family.emergency_contact_A_phone else "",
+ )
+
+ family.emergency_contact_A = contact_EA
+
+ if created:
+ created_contacts += 1
+
+ # -------------------------
+ # Emergency B
+ # -------------------------
+ if family.emergency_contact_B_firstname or family.emergency_contact_B_lastname:
+
+ contact_EB, created = ContactPerson.objects.get_or_create(
+ first_name=family.emergency_contact_B_firstname.strip() if family.emergency_contact_B_firstname else "",
+ last_name=family.emergency_contact_B_lastname.strip() if family.emergency_contact_B_lastname else "",
+ phone=family.emergency_contact_B_phone.strip() if family.emergency_contact_B_phone else "",
+ )
+
+ family.emergency_contact_B = contact_EB
+
+ if created:
+ created_contacts += 1
+
+ family.save()
+ updated_families += 1
+
+print(f"Families updated: {updated_families}")
+print(f"Contacts created: {created_contacts}")
\ No newline at end of file
diff --git a/busManager/migration_scripts/populate_locations.py b/busManager/migration_scripts/populate_locations.py
new file mode 100644
index 0000000..573dcc3
--- /dev/null
+++ b/busManager/migration_scripts/populate_locations.py
@@ -0,0 +1,38 @@
+from setup_django import *
+
+from traveller.models import Family
+from locations.models import Suburb, Location
+
+for family in Family.objects.all():
+ residential_address = family.residential_address
+ residential_suburb = family.residential_suburb
+ postal_address = family.postal_address
+ postal_suburb = family.postal_suburb
+
+ if residential_address and residential_suburb:
+ suburb = Suburb.objects.get(pk=residential_suburb.id)
+ location, created = Location.objects.get_or_create(
+ address=residential_address,
+ suburb=suburb,
+ )
+ family.location = location
+
+ if postal_address and postal_suburb:
+ suburb = Suburb.objects.get(pk=postal_suburb.id)
+ location, created = Location.objects.get_or_create(
+ address=postal_address,
+ suburb=suburb,
+ )
+ family.postal_location = location
+
+ family.save()
+
+residential_count = Family.objects.filter(
+ location__isnull=False
+).count()
+postal_count = Family.objects.filter(
+ postal_location__isnull=False
+).count()
+
+print(f"Residential count: {residential_count}")
+print(f"Postal count: {postal_count}")
\ No newline at end of file
diff --git a/busManager/transport/context_helpers.py b/busManager/transport/context_helpers.py
index 34bac90..f805661 100644
--- a/busManager/transport/context_helpers.py
+++ b/busManager/transport/context_helpers.py
@@ -1,5 +1,7 @@
+from django.db.models import Prefetch
+
from transport.models import Bus, Driver, BusStop, Shuttle
-from traveller.models import TravellerRoute, Traveller
+from traveller.models import TravellerRoute, Traveller, Family
def bus_summary_context():
@@ -52,37 +54,83 @@ def emergency_contacts_context(queryset=None):
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})
+ buses = buses.prefetch_related(
+ Prefetch(
+ "driver_set",
+ queryset=Driver.objects.all()
+ ),
+ Prefetch(
+ "busstop_set__traverseroute_set",
+ queryset=TravellerRoute.objects.select_related(
+ "traveller",
+ "busStop"
+ ).prefetch_related(
+ Prefetch(
+ "traveller__family_set",
+ queryset=Family.objects.select_related(
+ "contact_A",
+ "contact_B",
+ "emergency_contact_A",
+ "emergency_contact_B",
+ )
+ )
+ )
+ )
+ )
- return {'routes': bus_routes}
\ No newline at end of file
+ bus_routes = []
+
+ for bus in buses:
+
+ drivers = list(bus.driver_set.all())
+
+ traveller_list = []
+
+ traveller_routes = TravellerRoute.objects.filter(
+ busStop__bus=bus
+ ).select_related(
+ "traveller"
+ )
+
+ for travellerRoute in traveller_routes:
+
+ traveller = travellerRoute.traveller
+
+ if not traveller.is_active:
+ continue
+
+ for family in traveller.family_set.all():
+
+ parent_a = ""
+ parent_b = ""
+ contact_a = ""
+ contact_b = ""
+
+ if family.contact_A:
+ parent_a = f"{family.contact_A.first_name} {family.contact_A.last_name} ({family.contact_A.phone})"
+
+ if family.contact_B:
+ parent_b = f"{family.contact_B.first_name} {family.contact_B.last_name} ({family.contact_B.phone})"
+
+ if family.emergency_contact_A:
+ contact_a = f"{family.emergency_contact_A.first_name} {family.emergency_contact_A.last_name} ({family.emergency_contact_A.phone})"
+
+ if family.emergency_contact_B:
+ contact_b = f"{family.emergency_contact_B.first_name} {family.emergency_contact_B.last_name} ({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}
\ No newline at end of file
diff --git a/busManager/traveller/admin.py b/busManager/traveller/admin.py
index 6716c87..05007bd 100644
--- a/busManager/traveller/admin.py
+++ b/busManager/traveller/admin.py
@@ -1,11 +1,13 @@
from django.contrib import admin
+from django.db import models
+from django.urls import reverse
from django.utils.html import format_html
from rangefilter.filters import DateRangeFilterBuilder
from common.admin import MyImportExportModelAdmin
from traveller.adminClone import CloneModelAdmin
from traveller.admin_mixins import SchoolRollMixin, TravellerRollMixin
-from traveller.models import Family, TravellerRoute, Traveller, School
+from traveller.models import Family, TravellerRoute, Traveller, School, ContactPerson
class FamilyInline(admin.StackedInline):
@@ -13,6 +15,70 @@ class FamilyInline(admin.StackedInline):
classes = ['collapse']
extra = 0
clone_parent = "traveller"
+ autocomplete_fields = [
+ "location",
+ "postal_location",
+ "contact_A",
+ "contact_B",
+ "emergency_contact_A",
+ "emergency_contact_B",
+ ]
+
+ readonly_fields = [
+ "contact_A_phone_display",
+ "contact_B_phone_display",
+ "emergency_contact_A_phone_display",
+ "emergency_contact_B_phone_display",
+ ]
+
+ fieldsets = (
+ ("Residential Address", {
+ "fields": ("location",)
+ }),
+
+ ("Postal Address", {
+ "fields": ("postal_location",)
+ }),
+
+ ("Parents / Guardians", {
+ "fields": (
+ "contact_A_relation",
+ "contact_A",
+ "contact_A_phone_display",
+ "contact_B_relation",
+ "contact_B",
+ "contact_B_phone_display",
+ )
+ }),
+
+ ("Emergency Contacts", {
+ "fields": (
+ "emergency_contact_A_relation",
+ "emergency_contact_A",
+ "emergency_contact_A_phone_display",
+ "emergency_contact_B_relation",
+ "emergency_contact_B",
+ "emergency_contact_B_phone_display",
+ )
+ }),
+ )
+
+ def contact_A_phone_display(self, obj):
+ return obj.contact_A.phone if obj.contact_A else "-"
+
+ def contact_B_phone_display(self, obj):
+ return obj.contact_B.phone if obj.contact_B else "-"
+
+ def emergency_contact_A_phone_display(self, obj):
+ return obj.emergency_contact_A.phone if obj.emergency_contact_A else "-"
+
+ def emergency_contact_B_phone_display(self, obj):
+ return obj.emergency_contact_B.phone if obj.emergency_contact_B else "-"
+
+ contact_A_phone_display.short_description = "Phone"
+ contact_B_phone_display.short_description = "Phone"
+ emergency_contact_A_phone_display.short_description = "Phone"
+ emergency_contact_B_phone_display.short_description = "Phone"
class TravellerRouteInline(admin.TabularInline):
@@ -87,6 +153,85 @@ class TravellerAdmin(MyImportExportModelAdmin, CloneModelAdmin, TravellerRollMix
# obj.is_archived = False
# super().save_model(request, obj, form, change)
+@admin.register(ContactPerson)
+class ContactPersonAdmin(MyImportExportModelAdmin):
+ list_display = [
+ "first_name",
+ "last_name",
+ "phone",
+ "email"
+ ]
+
+ search_fields = [
+ "first_name",
+ "last_name",
+ "phone",
+ "email",
+ ]
+
+ readonly_fields = [
+ "traveller_relationship_summary"
+ ]
+
+ fieldsets = (
+ (None, {
+ "fields": ("first_name", "last_name", "phone", "email")
+ }),
+
+ ("Relationships", {
+ "fields": ("traveller_relationship_summary",)
+ }),
+ )
+
+ def traveller_relationship_summary(self, obj):
+ from django.utils.html import format_html
+
+ families = Family.objects.filter(
+ models.Q(contact_A=obj) |
+ models.Q(contact_B=obj) |
+ models.Q(emergency_contact_A=obj) |
+ models.Q(emergency_contact_B=obj)
+ ).select_related("traveller", "location")
+
+ if not families.exists():
+ return "No traveller relationships"
+
+ output = []
+
+ for family in families:
+ traveller = family.traveller
+
+ relation = ""
+ if family.contact_A == obj:
+ relation = family.contact_A_relation
+ elif family.contact_B == obj:
+ relation = family.contact_B_relation
+ elif family.emergency_contact_A == obj:
+ relation = family.emergency_contact_A_relation
+ elif family.emergency_contact_B == obj:
+ relation = family.emergency_contact_B_relation
+
+ address = family.location if family.location else "No address"
+
+ url = reverse(
+ "admin:traveller_traveller_change",
+ args=[traveller.id]
+ )
+
+ output.append(
+ format_html(
+ '{} ({})
'
+ '{}',
+ url,
+ f"{traveller.first_name} {traveller.last_name}",
+ relation,
+ address
+ )
+ )
+
+ return format_html("".join(output))
+
+ traveller_relationship_summary.short_description = "Traveller Relationships"
# @admin.register(Family)
class FamilyAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
diff --git a/busManager/traveller/models.py b/busManager/traveller/models.py
index 2487719..0ac0f29 100644
--- a/busManager/traveller/models.py
+++ b/busManager/traveller/models.py
@@ -1,10 +1,10 @@
from datetime import datetime
-import phonenumbers
from django.core.exceptions import ValidationError
from django.db import models
from common.models import Suburb
+from locations.models import Location
class School(models.Model):
@@ -152,6 +152,20 @@ class Traveller(models.Model):
def get_families(self):
return Family.objects.filter(traveller__id__exact=self.id)
+class ContactPerson(models.Model):
+ first_name = models.CharField(max_length=100, blank=True)
+ last_name = models.CharField(max_length=100, blank=True)
+ phone = models.CharField(max_length=15, blank=True)
+ email = models.EmailField(blank=True)
+
+ class Meta:
+ indexes = [
+ models.Index(fields=["phone"]),
+ models.Index(fields=["last_name"]),
+ ]
+
+ def __str__(self):
+ return f"{self.first_name} {self.last_name}"
class Family(models.Model):
RELATIONS = [
@@ -169,6 +183,9 @@ class Family(models.Model):
]
traveller = models.ForeignKey(Traveller, on_delete=models.CASCADE)
+ location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True)
+ postal_location = models.ForeignKey(Location, on_delete=models.PROTECT, null=True, blank=True,
+ related_name="family_postal_locations")
residential_address = models.CharField(max_length=50, blank=True)
residential_suburb = models.ForeignKey(Suburb, on_delete=models.PROTECT, blank=True, null=True,
related_name='family_residential_suburb')
@@ -191,14 +208,29 @@ class Family(models.Model):
emergency_contact_B_lastname = models.CharField(max_length=50, blank=True)
emergency_contact_B_phone = models.CharField(max_length=15, blank=True)
emergency_contact_B_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
+ contact_A_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
+ contact_A = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_contact_A')
+ contact_B_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
+ contact_B = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_contact_B')
+ emergency_contact_A = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_emergency_contact_A')
+ emergency_contact_B = models.ForeignKey(ContactPerson, on_delete=models.SET_NULL, blank=True, null=True, related_name='family_emergency_contact_B')
created_on = models.DateTimeField(auto_now_add=True, blank=True, null=True)
last_edit = models.DateTimeField(auto_now=True, blank=True, null=True)
class Meta:
verbose_name_plural = "Families"
+ indexes = [
+ models.Index(fields=["contact_A"]),
+ models.Index(fields=["contact_B"]),
+ models.Index(fields=["emergency_contact_A"]),
+ models.Index(fields=["emergency_contact_B"]),
+ ]
def __str__(self):
- return self.parent_names()
+ name = self.parent_names()
+ if name:
+ return f"{self.traveller}: {name}"
+ return str(self.traveller)
def clean(self):
valid_numbers, failed_numbers = self.get_parsed_numbers(True, True)
@@ -206,8 +238,8 @@ class Family(models.Model):
raise ValidationError(f"Phone number {failed_numbers[0]} not valid")
def parent_names(self):
- a_name = self.parent_A_firstname
- b_name = self.parent_B_firstname
+ a_name = self.contact_A.first_name
+ b_name = self.contact_B.first_name
if a_name:
if b_name:
return f"{a_name} and {b_name}"
@@ -217,24 +249,24 @@ class Family(models.Model):
return ""
def get_parsed_numbers(self, parents=False, emergency=False):
+ import phonenumbers
numbers = []
if parents:
- if self.parent_A_phone:
- numbers.append(self.parent_A_phone)
- if self.parent_B_phone:
- numbers.append(self.parent_B_phone)
+ for contact in [self.contact_A, self.contact_B]:
+ if contact and contact.phone:
+ numbers.append(contact.phone)
if emergency:
- if self.emergency_contact_A_phone:
- numbers.append(self.emergency_contact_A_phone)
- if self.emergency_contact_B_phone:
- numbers.append(self.emergency_contact_B_phone)
+ for contact in [self.emergency_contact_A, self.emergency_contact_B]:
+ if contact and contact.phone:
+ numbers.append(contact.phone)
+
valid_numbers = []
failed_numbers = []
for number in numbers:
try:
- num = phonenumbers.parse(number, "AU")
- if phonenumbers.is_valid_number(num):
- valid_numbers.append(f"+{num.country_code}{num.national_number}")
+ parsed = phonenumbers.parse(number, "AU")
+ if phonenumbers.is_valid_number(parsed):
+ valid_numbers.append(f"+{parsed.country_code}{parsed.national_number}")
else:
failed_numbers.append(number)
except: