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: