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 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('{} Buses', url, count) def email(self, obj): return format_html('{}', 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('{} Travellers', 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('{}', obj.email, obj.email) @admin.register(Setting) class SettingAdmin(MyImportExportModelAdmin, admin.ModelAdmin): list_display = ["name", "value"] @admin.register(Shuttle) class ShuttleAdmin(MyImportExportModelAdmin, admin.ModelAdmin): list_display = ["__str__", "school", "bus", "shuttle_travellers"] def shuttle_travellers(self, obj): url = ( reverse("admin:coord_traveller_changelist") + "?" + urlencode({"shuttle__id__exact": f"{obj.id}"}) ) return format_html('{} Travellers', 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('{}', url, obj.bus)