Added locations app

Moved contacts to new model
This commit is contained in:
st01765
2026-02-26 13:34:32 +11:00
parent a24daed8f6
commit 4025c28eae
25 changed files with 576 additions and 75 deletions
+146 -1
View File
@@ -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(
'<a href="{}">{}</a> ({})<br>'
'<small style="color:#888">{}</small>',
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):
+47 -15
View File
@@ -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: