Initial commit

This commit is contained in:
John Mullins
2023-08-18 18:10:53 +10:00
commit 0c3ba727bf
25 changed files with 1214 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
# Django #
*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media
# Backup files #
*.bak
# If you are using PyCharm #
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# File-based project format
*.iws
# IntelliJ
out/
# JIRA plugin
atlassian-ide-plugin.xml
# Python #
*.py[cod]
*$py.class
# Distribution / packaging
.Python build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery
celerybeat-schedule.*
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mkdocs documentation
/site
# mypy
.mypy_cache/
# sftp configuration file
sftp-config.json
# Package control specific files Package
Control.last-run
Control.ca-list
Control.ca-bundle
Control.system-ca-bundle
GitHub.sublime-settings
# Visual Studio Code #
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history
/busManager/busManager/settings.py
/busManager/coord/migrations
/busManager/coord/adminClone.py
/busManager/coord/adminCloneOld.py
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+1
View File
@@ -0,0 +1 @@
View File
+16
View File
@@ -0,0 +1,16 @@
"""
ASGI config for busManager project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'busManager.settings')
application = get_asgi_application()
+125
View File
@@ -0,0 +1,125 @@
"""
Django settings for busManager project.
Generated by 'django-admin startproject' using Django 4.2.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ''
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'coord.apps.CoordConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'import_export',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'busManager.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / "busManager/templates"],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'busManager.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Australia/Melbourne'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+25
View File
@@ -0,0 +1,25 @@
"""
URL configuration for busManager project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
admin.site.site_header = "Bus Portal Admin"
urlpatterns = [
path('report/', include("coord.urls")),
path('admin/', admin.site.urls),
]
+16
View File
@@ -0,0 +1,16 @@
"""
WSGI config for busManager project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'busManager.settings')
application = get_wsgi_application()
View File
+199
View File
@@ -0,0 +1,199 @@
import csv
from django.contrib.admin.utils import unquote
from django.core.exceptions import PermissionDenied
from django.forms import widgets
from django.contrib import admin
from django.http import HttpResponse, Http404
from django.urls import reverse
from django.utils.translation import gettext_lazy
from django.utils.html import format_html
from django.utils.http import urlencode
from import_export.admin import ImportExportModelAdmin
from .adminClone import ClonableModelAdmin
from .models import *
from .views import bus_roll
class ExportCsvMixin:
def export_as_csv(self, request, queryset):
meta = self.model._meta
field_names = [field.name for field in meta.fields]
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename={}.csv'.format(meta)
writer = csv.writer(response)
writer.writerow(field_names)
for obj in queryset:
row = writer.writerow([getattr(obj, field) for field in field_names])
return response
export_as_csv.short_description = "Export Selected"
class BusRollMixin:
def bus_roll(self, request, queryset):
return bus_roll(request, queryset)
def email_company(self, request, queryset):
pass
email_company.short_description = "Email Bus Roll to Company"
class HiddenNowTime(widgets.TimeInput):
pass
class MyImportExportModelAdmin(ImportExportModelAdmin):
def has_import_permission(self, request):
return request.user.is_superuser
@admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
list_display = ["name", "buses"]
def buses(self, obj):
count = obj.bus_set.count()
url = (
reverse("admin:coord_bus_changelist")
+ "?"
+ urlencode({"company__id": f"{obj.id}"})
)
return format_html('<a href="{}">{} Buses</a>', url, count)
class DriverInline(admin.StackedInline):
model = Driver
extra = 1
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"]
actions = ["email_company", "bus_roll"]
inlines = [DriverInline, BusStopInline]
@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"]
# change_form_template = 'admin/change_form_busStops.html'
def export_as_csv(self, request, queryset):
pass
@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, admin.ModelAdmin):
list_display = ["first_name", "last_name", "school", "year_level", "residential_address", "residential_suburb"]
list_filter = ["school", "eligibility_status", "bus_stops__bus", "residential_suburb"]
search_fields = ["first_name", "last_name", "residential_address"]
inlines = [TravellerRouteInline]
readonly_fields = ["fare_paying"]
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",
"assessment_date",
"application_form_completed",
"parent_notified",
"seat_number",
]
}),
('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
actions = ["yearly_rollover"]
def yearly_rollover(self, request, queryset):
pass
@admin.register(TravellerRoute)
class TravellerRouteAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
list_display = ["traveller", "busStop"]
@admin.register(School)
class SchoolAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
pass
@admin.register(Setting)
class SettingAdmin(MyImportExportModelAdmin, admin.ModelAdmin):
list_display = ["name", "value"]
admin.site.register(Shuttle)
admin.site.register(Driver)
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class CoordConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'coord'
+282
View File
@@ -0,0 +1,282 @@
from datetime import datetime
from django.db import models
from django.db.models import Model
class Setting(models.Model):
name = models.CharField(max_length=20, unique=True)
value = models.CharField(max_length=20, blank=True)
def __str__(self):
return self.name
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 School(models.Model):
name = models.CharField(max_length=30, unique=True)
shortName = models.CharField(max_length=10, unique=True)
address = models.CharField(max_length=50)
suburb = models.ForeignKey(Suburb, on_delete=models.CASCADE)
email = models.CharField(max_length=50, blank=True)
phone = models.CharField(max_length=15, blank=True)
principal_name = models.CharField(max_length=50, blank=True)
principal_phone = models.CharField(max_length=15, blank=True)
notes = models.TextField(blank=True)
def __str__(self):
return self.name
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}"
class Shuttle(models.Model):
bus = models.ForeignKey(Bus, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
school = models.ForeignKey(School, on_delete=models.CASCADE)
class Meta:
ordering = ["school__name"]
def __str__(self):
return f"{self.name}, {self.bus.route_name} -> {self.school.name}"
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} - {self.bus.route_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}"
class Traveller(models.Model):
YEAR = [
("PS", "PreSchool"),
("00", "Year 00"),
("01", "Year 01"),
("02", "Year 02"),
("03", "Year 03"),
("04", "Year 04"),
("05", "Year 05"),
("06", "Year 06"),
("07", "Year 07"),
("08", "Year 08"),
("09", "Year 09"),
("10", "Year 10"),
("11", "Year 11"),
("12", "Year 12"),
("AL", "Adult Learner"),
]
ELIGIBILITY_STATUS = [
("1", "Eligible"),
("2", "Fare Payer"),
("3", "<4.8 Exemption"),
("4", "Eligible waitlisted"),
("5", "Ineligible waitlisted"),
("6", "Kinder Exemption"),
("7", "Tafe/Post Secondary Exemption"),
("8", "Other Exemption"),
]
RELATIONS = [
("1", "Parent"),
("2", "Step-Parent"),
("3", "Foster Parent"),
("4", "Host Family"),
("5", "Sibling"),
("6", "Grandparent"),
("7", "Aunt/Uncle"),
("8", "Cousin"),
("9", "Carer"),
("10", "Case Worker"),
("11", "Friend/Other"),
]
school = models.ForeignKey(School, on_delete=models.PROTECT)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
dob = models.DateField(blank=True, null=True)
year_level = models.CharField(max_length=2, choices=YEAR)
bus_stops = models.ManyToManyField(BusStop, through='TravellerRoute', blank=True)
distance_to_school = models.PositiveSmallIntegerField(blank=True, null=True)
residential_address = models.CharField(max_length=50, blank=True)
residential_suburb = models.ForeignKey(Suburb, on_delete=models.PROTECT, blank=True, null=True, related_name='residential_suburb')
postal_address = models.CharField(max_length=50, blank=True)
postal_suburb = models.ForeignKey(Suburb, on_delete=models.PROTECT, blank=True, null=True, related_name='postal_suburb')
travel_start_date = models.DateField(blank=True, null=True)
travel_end_date = models.DateField(blank=True, null=True)
eligibility_status = models.CharField(max_length=1, choices=ELIGIBILITY_STATUS)
assessment_date = models.DateField(blank=True, null=True)
fee_per_term = models.DecimalField(decimal_places=2, max_digits=5, blank=True, null=True)
application_form_completed = models.BooleanField()
parent_notified = models.BooleanField()
seat_number = models.CharField(max_length=5, blank=True)
parent_A_firstname = models.CharField(max_length=50, blank=True)
parent_A_lastname = models.CharField(max_length=50, blank=True)
parent_A_phone = models.CharField(max_length=15, blank=True)
parent_A_email = models.CharField(max_length=50, blank=True)
parent_B_firstname = models.CharField(max_length=50, blank=True)
parent_B_lastname = models.CharField(max_length=50, blank=True)
parent_B_phone = models.CharField(max_length=15, blank=True)
parent_B_email = models.CharField(max_length=50, blank=True)
emergency_contact_A_firstname = models.CharField(max_length=50, blank=True)
emergency_contact_A_lastname = models.CharField(max_length=50, blank=True)
emergency_contact_A_phone = models.CharField(max_length=15, blank=True)
emergency_contact_A_relation = models.CharField(max_length=50, choices=RELATIONS, blank=True)
emergency_contact_B_firstname = models.CharField(max_length=50, blank=True)
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)
notes = models.TextField(blank=True, verbose_name='Admin Notes')
shuttle = models.ForeignKey(Shuttle, on_delete=models.SET_NULL, blank=True, null=True)
class Meta:
ordering = ["last_name"]
def __str__(self):
return f"{self.first_name} {self.last_name}"
def is_active(self):
if not self.travel_end_date:
return True
return datetime(self.travel_end_date.year, self.travel_end_date.month, self.travel_end_date.day) < datetime.today()
def fare_paying(self):
if self.eligibility_status != "2":
return
cost_setting = Setting.objects.filter(name="TERM_TRAVEL_COST")
if not cost_setting.exists():
return "TERM_TRAVEL_COST not configured"
cost = int(cost_setting.get().value)
stops = 0
for stop in TravellerRoute.objects.filter(traveller=self.id):
stops += stop.active_stops()
if stops > 1:
stops = 1
return f"${str(cost*stops)}"
class TravellerRoute(models.Model):
traveller = models.ForeignKey(Traveller, on_delete=models.CASCADE)
busStop = models.ForeignKey(BusStop, on_delete=models.CASCADE)
mon_am = models.BooleanField(default=True)
mon_pm = models.BooleanField(default=True)
tue_am = models.BooleanField(default=True)
tue_pm = models.BooleanField(default=True)
wen_am = models.BooleanField(default=True)
wen_pm = models.BooleanField(default=True)
thu_am = models.BooleanField(default=True)
thu_pm = models.BooleanField(default=True)
fri_am = models.BooleanField(default=True)
fri_pm = models.BooleanField(default=True)
notes = models.TextField(blank=True, verbose_name="Driver Notes")
def __str__(self):
return f"{self.busStop}"
def active_stops(self):
stops = 0
if self.mon_am:
stops += 1
if self.mon_pm:
stops += 1
if self.tue_am:
stops += 1
if self.tue_pm:
stops += 1
if self.wen_am:
stops += 1
if self.wen_pm:
stops += 1
if self.thu_am:
stops += 1
if self.thu_pm:
stops += 1
if self.fri_am:
stops += 1
if self.fri_pm:
stops += 1
return stops * 0.1
@@ -0,0 +1,7 @@
{% extends 'admin/change_form.html' %}
{% load i18n admin_urls %}
{% block object-tools-items %}
<li><a href="clone/" class="addlink">{{ clone_verbose_name }}</a></li>
{{ block.super }}
{% endblock %}
@@ -0,0 +1,46 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static admin_modify %}
{% block extrahead %}{{ block.super }}
<script src="{% url 'admin:jsi18n' %}"></script>
{{ media }}
{% endblock %}
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" href="{% static "admin/css/forms.css" %}">{% endblock %}
{% block coltype %}colM{% endblock %}
{% block bodyclass %}{{ block.super }} app-{{ app_label }} model-{{ verbose_name }} change-form{% endblock %}
{% if not is_popup %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
<a href="{% url 'admin:app_list' app_label='coord' %}">{{ verbose_name }}</a>
&rsaquo; {% if has_view_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
&rsaquo; {% if add %}{% blocktranslate with name=verbose_name %}Add {{ name }}{% endblocktranslate %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
</div>
{% endblock %}
{% endif %}
{% block content %}<div id="content-main">
{% block after_field_sets %}{% endblock %}
{% block inline_field_sets %}
{% endblock %}
{% block after_related_objects %}{% endblock %}
{% block submit_buttons_bottom %}{% endblock %}
{% block admin_change_form_document_ready %}
{% endblock %}
</div>
{% endblock %}
@@ -0,0 +1,17 @@
{% load i18n admin_urls %}
<div class="submit-row">
{% block submit-row %}
{% if show_save %}<input type="submit" value="{% translate 'Save' %}" class="default" name="_save">{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% translate 'Save as new' %}" name="_saveasnew">{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% translate 'Save and add another' %}" name="_addanother">{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% if can_change %}{% translate 'Save and continue editing' %}{% else %}{% translate 'Save and view' %}{% endif %}" name="_continue">{% endif %}
{% if show_close %}
{% url opts|admin_urlname:'changelist' as changelist_url %}
<a href="{% add_preserved_filters changelist_url %}" class="closelink">{% translate 'Close' %}</a>
{% endif %}
{% if show_delete_link and original %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<a href="{% add_preserved_filters delete_url %}" class="deletelink">{% translate "Delete" %}</a>
{% endif %}
{% endblock %}
</div>
+12
View File
@@ -0,0 +1,12 @@
<h1>Company List</h1>
<table>
{% for item in object_list %}
<tr>
<td>{{ item.name }}</td>
<td>{{ item.contact_name }}</td>
<td>{{ item.contact_number }}</td>
<td>{{ item.contact_email }}</td>
<td>{{ item.address }}</td>
</tr>
{% endfor %}
</table>
@@ -0,0 +1,13 @@
<h1>Bus Numbers</h1>
<table>
<tr>
<th>Route Name</th>
<th>Number of Students</th>
</tr>
{% for bus in buses %}
<tr>
<td>{{ bus.route_name }}</td>
<td>{{ bus.num_travellers }}</td>
</tr>
{% endfor %}
</table>
@@ -0,0 +1,80 @@
<style>
table.stopHeader th {
text-align: left;
min-width: 150px;
width: 100%;
}
table.traveller th {
text-align: left;
border-bottom: 2px solid #000;
}
table.traveller td {
table-layout: fixed;
border-bottom: 2px solid #000;
border-right-style: dashed;
width: 100%;
min-width: 30px;
}
hr {
border: 2px solid;
}
</style>
{% for route in routes %}
<h1>{{ route.route_name }}</h1>
{% for stop in route.stops %}
<hr>
<table class="stopHeader">
<tr>
<th>Stop Number #{{ stop.stop_num }}</th>
<th>Pickup Time</th>
<th>Drop-off Time</th>
</tr>
<tr>
<td>{{ stop.name }}</td>
<td>{{ stop.am }}</td>
<td>{{ stop.pm }}</td>
</tr>
</table>
<table class="traveller">
<tr>
<th>Student</th>
<th>Fare</th>
<th>Mon AM</th>
<th>Mon PM</th>
<th>Tue AM</th>
<th>Tue PM</th>
<th>Wed AM</th>
<th>Wed PM</th>
<th>Thu AM</th>
<th>Thu PM</th>
<th>Fri AM</th>
<th>Fri PM</th>
</tr>
{% for traveller in stop.travellers %}
<tr>
<td>{{ traveller.display }}</td>
<td><b>{{ traveller.isFared }}</b></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
{% endfor %}
</table>
<br>
{% endfor %}
<p style="page-break-after: always">
{% endfor %}
@@ -0,0 +1,33 @@
{% for route in routes %}
<h1>{{ route.route_name }}</h1>
{% for stop in route.stops %}
<tr>
<th>Stop Number</th>
<th>Address</th>
<th>Pickup Time</th>
<th>Drop-off Time</th>
</tr>
<tr>
<th>{{ stop.stop_num }}</th>
<th>{{ stop.name }}</th>
<th>{{ stop.am }}</th>
<th>{{ stop.pm }}</th>
</tr>
{% for traveller in stop.travellers %}
<tr>
<th>Student</th>
<th>Mon AM</th>
<th>Mon PM</th>
<th>Tue AM</th>
<th>Tue PM</th>
</tr>
<tr>
<th>{{ traveller.traveller }}</th>
<th>{{ traveller.mon_am }}</th>
<th>{{ traveller.mon_pm }}</th>
<th>{{ traveller.tue_am }}</th>
<th>{{ traveller.tue_pm }}</th>
</tr>
{% endfor %}
{% endfor %}
{% endfor %}
@@ -0,0 +1,50 @@
<style>
table.stopHeader th {
text-align: left;
}
table.traveller th {
text-align: left;
border-bottom: 2px solid #000;
}
table.traveller td {
table-layout: fixed;
border-bottom: 2px solid #000;
border-right-style: dashed;
}
hr {
border: 2px solid;
}
</style>
{% for route in routes %}
<h1>{{ route.bus }}</h1>
<hr>
<table class="traveller">
<tr>
<th>Student</th>
<th>Parent A</th>
<th>Parent B</th>
<th>Emergency Contact A</th>
<th>Emergency Contact B</th>
<th>Driver notes</th>
</tr>
{% for traveller in route.travellers %}
<tr>
<td>{{ traveller.traveller }} ({{ traveller.traveller.school.shortName }})</td>
<td>{{ traveller.parent_a }}</td>
<td>{{ traveller.parent_b }}</td>
<td>{{ traveller.contact_a }}</td>
<td>{{ traveller.contact_b }}</td>
<td>{{ traveller.note }}</td>
</tr>
{% endfor %}
</table>
<br>
<p style="page-break-after: always">
{% endfor %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+10
View File
@@ -0,0 +1,10 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.bus_numbers, name="index"),
path("roll", views.bus_roll, name="Student Roll"),
path("clone", views.clone, name="Clone"),
path("contacts", views.emergency_contacts, name="Emergency Contacts")
]
+109
View File
@@ -0,0 +1,109 @@
from django.contrib.admin.views.decorators import staff_member_required
from django.http import HttpResponse
from django.shortcuts import render
from django.views.generic import ListView
from .models import Company, Bus, Traveller, BusStop, TravellerRoute
# def index(request):
# return HttpResponse("Hello, world. You're at the coord index.")
@staff_member_required
def bus_numbers(request):
buses = []
for bus in Bus.objects.all():
num_travellers = Traveller.objects.filter(bus_stops__bus=bus).count()
buses.append({'route_name': bus.route_name, 'num_travellers': num_travellers})
return render(request, 'reports/bus_numbers.html', {'buses': buses})
@staff_member_required
def bus_roll(request, queryset=None):
bus_routes = []
if queryset is None:
buses = Bus.objects.all()
else:
buses = queryset
for bus in buses:
bus_route = []
for bus_stop in BusStop.objects.filter(bus=bus):
traveller_list = []
for trav_route in TravellerRoute.objects.filter(busStop=bus_stop):
traveller = trav_route.traveller
if not traveller.is_active():
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_result = {
'stop_num': bus_stop.get_stop_number(),
'name': bus_stop.address,
'am': bus_stop.am_time,
'pm': bus_stop.pm_time,
'travellers': traveller_list
}
# print(traveller_list)
bus_route.append(stop_result)
bus_routes.append({'route_name': bus.route_name, 'stops': bus_route})
return render(request, 'reports/bus_roll.html', {'routes': bus_routes})
@staff_member_required
def emergency_contacts(request, queryset=None):
if queryset is None:
buses = Bus.objects.all()
else:
buses = queryset
bus_routes = []
for bus in buses:
traveller_list = []
for travellerRoute in TravellerRoute.objects.filter(busStop__bus=bus):
traveller = travellerRoute.traveller
parent_a = ""
if travellerRoute.traveller.guardian_A_firstname:
parent_a = f"{traveller.guardian_A_firstname} {traveller.guardian_A_lastname} ({traveller.guardian_A_phone})"
parent_b = ""
if travellerRoute.traveller.guardian_B_firstname:
parent_b = f"{traveller.guardian_B_firstname} {traveller.guardian_B_lastname} ({traveller.guardian_B_phone})"
contact_a = ""
if travellerRoute.traveller.emergency_contact_A_firstname:
contact_a = f"{traveller.emergency_contact_A_firstname} {traveller.emergency_contact_A_lastname} ({traveller.emergency_contact_A_phone})"
contact_b = ""
if travellerRoute.traveller.emergency_contact_B_firstname:
contact_b = f"{traveller.emergency_contact_B_firstname} {traveller.emergency_contact_B_lastname} ({traveller.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, 'travellers': traveller_list})
return render(request, 'reports/emergency_contacts.html', {'routes': bus_routes})
@staff_member_required
def clone(request):
options = {
'verbose_name': "Traveller",
'app_label': "coord"
}
return render(request, 'admin/clone_form.html', options)
@staff_member_required
class CompanyList(ListView):
model = Company
template_name = "companies.html"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'busManager.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
BIN
View File
Binary file not shown.