3 Commits

12 changed files with 219 additions and 66 deletions

View File

@@ -43,6 +43,7 @@ INSTALLED_APPS = [
'colorfield', 'colorfield',
'import_export', 'import_export',
'django_admin_action_forms', 'django_admin_action_forms',
'polymorphic'
] ]
# Import Export plugin settings # Import Export plugin settings
@@ -52,7 +53,7 @@ IMPORT_EXPORT_SKIP_ADMIN_LOG = True
IMPORT_FORMATS = [CSV] IMPORT_FORMATS = [CSV]
MIDDLEWARE = [ MIDDLEWARE = [
'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware', 'flightslot.middleware.RedirectNonSuperuserFromAdminMiddleware', # custom middleware to show "user" page to non superuser
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',

View File

@@ -9,7 +9,3 @@ urlpatterns = [
path('user/', flightslot_user.urls), path('user/', flightslot_user.urls),
path("", lambda r: redirect("/user/")), # la root porta gli utenti nella pagina giusta path("", lambda r: redirect("/user/")), # la root porta gli utenti nella pagina giusta
] ]
admin.site.site_header = "Flight Scheduler 🛫"
admin.site.site_title = "Flight Scheduler 🛫"
admin.site.index_title = "Welcome to CantorAir Flight Scheduler Portal"

View File

@@ -10,7 +10,7 @@ from typing import List
from ..models.weekpref import WeekPreference from ..models.weekpref import WeekPreference
from ..models.missions import Training from ..models.missions import Training
from ..models.hourbuildings import HourBuilding,HourBuildingLeg from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse: def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
@@ -121,9 +121,9 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
hb_name if h.sunday else "" hb_name if h.sunday else ""
] ]
hb_notes = f"{h.notes}\n----\n" if h.notes else "" hb_notes = f"{h.notes}\n----\n" if h.notes else ""
hb_legs = HourBuildingLeg.objects.filter(hb_id = h.id) hb_legs = HourBuildingLegFlight.objects.filter(hb_id = h.id)
for hh in hb_legs: #for hh in hb_legs:
hb_notes += f"{hh.departure} -> {hh.destination} [{hh.time}]\n" if not hh.stop else f"STOP at {hh.departure} [{hh.time}]\n" # hb_notes += f"{hh.departure} -> {hh.destination} [{hh.time}]\n" if not hh.stop else f"STOP at {hh.departure} [{hh.time}]\n"
hb_notes.strip('\n') hb_notes.strip('\n')
hb_data.append([str(q.week), *student_data, *hb_days, str(q.student.phone), q.student.email, hb_notes]) hb_data.append([str(q.week), *student_data, *hb_days, str(q.student.phone), q.student.email, hb_notes])

View File

@@ -10,6 +10,7 @@ from .admins.course_adm import CourseAdmin
from .admins.student_adm import StudentAdmin from .admins.student_adm import StudentAdmin
from .admins.mission_adm import MissionProfileAdmin from .admins.mission_adm import MissionProfileAdmin
from .admins.weekpred_adm import WeekPreferenceAdmin from .admins.weekpred_adm import WeekPreferenceAdmin
#from .admins.hourbuilding_adm import HourBuilding, HourBuildingInLine
from django.contrib.admin import AdminSite from django.contrib.admin import AdminSite
@@ -31,6 +32,10 @@ flightslot_user = FlightSlotUserSite(name="user_site")
# registra SOLO i modelli autorizzati # registra SOLO i modelli autorizzati
flightslot_user.register(WeekPreference, WeekPreferenceAdmin) flightslot_user.register(WeekPreference, WeekPreferenceAdmin)
admin.site.site_header = "Flight Scheduler Admin 🛫"
admin.site.site_title = "Flight Scheduler Admin 🛫"
admin.site.index_title = "Welcome to CantorAir Flight Scheduler Administrator Portal"
admin.site.register(Course, CourseAdmin) admin.site.register(Course, CourseAdmin)
admin.site.register(MissionProfile, MissionProfileAdmin) admin.site.register(MissionProfile, MissionProfileAdmin)
admin.site.register(Student, StudentAdmin) admin.site.register(Student, StudentAdmin)

View File

@@ -7,36 +7,63 @@ from django.http import HttpRequest
from durationwidget.widgets import TimeDurationWidget from durationwidget.widgets import TimeDurationWidget
from ..models.hourbuildings import HourBuilding, HourBuildingLeg from ..models.hourbuildings import HourBuilding, HourBuildingLegBase, HourBuildingLegFlight, HourBuildingLegStop
from ..models.weekpref import WeekPreference from ..models.weekpref import WeekPreference
from datetime import date from datetime import date
class HourBuildingLegForm(forms.ModelForm): class HourBuildingLegFlightForm(forms.ModelForm):
class Meta: class Meta:
model = HourBuildingLeg model = HourBuildingLegFlight
fields = '__all__' fields = "__all__"
widgets = { widgets = {
'time': TimeDurationWidget(show_days=False, "time": TimeDurationWidget(show_days=False,
show_seconds=False show_seconds=False,
attrs={
"style": (
"margin-right:5px; margin-left:5px; width:40px; min:0; max:5"
) )
})
}
class HourBuildingLegStopForm(forms.ModelForm):
class Meta:
model = HourBuildingLegStop
fields = "__all__"
widgets = {
"time": TimeDurationWidget(show_days=False,
show_seconds=False,
attrs={
"style": (
"margin-right:5px; margin-left:5px; width:40px;"
)
})
} }
# Register your models here. # Register your models here.
class HourBuildingLegInline(nested_admin.NestedTabularInline): class HourBuildingLegBaseInLine(nested_admin.NestedStackedPolymorphicInline):
model = HourBuildingLeg model = HourBuildingLegBase
form = HourBuildingLegForm fk_name = "hb"
extra = 0 verbose_name_plural = "Hour Building Legs"
fk_name = 'hb'
max_num = 5 class HourBuildingLegFlightInLine(nested_admin.NestedStackedPolymorphicInline.Child):
formfield_overrides = { model = HourBuildingLegFlight
models.CharField: {'widget': TextInput(attrs={'size':'20'})}, form = HourBuildingLegFlightForm
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':35})}, fk_name = "hourbuildinglegbase_ptr"
} fields = ("departure", "time", "destination", "pax", )
hide_title = True
class HourBuildingLegStopInLine(nested_admin.NestedStackedPolymorphicInline.Child):
model = HourBuildingLegStop
form = HourBuildingLegFlightForm
fk_name = "hourbuildinglegbase_ptr"
fields = ("time", "refuel", )
child_inlines = (HourBuildingLegFlightInLine, HourBuildingLegStopInLine, )
# If user is a student deny edit permission for week past the current one # If user is a student deny edit permission for week past the current one
def has_change_permission(self, request: HttpRequest, obj: HourBuilding | None = None): def has_change_permission(self, request: HttpRequest, obj: HourBuilding | None = None):
if hasattr(request.user, 'student') and obj: if hasattr(request.user, "student") and obj:
current_week = date.today().isocalendar().week current_week = date.today().isocalendar().week
if not obj.DoesNotExist and current_week > obj.weekpref.week: if not obj.DoesNotExist and current_week > obj.weekpref.week:
return False return False
@@ -45,26 +72,16 @@ class HourBuildingLegInline(nested_admin.NestedTabularInline):
def has_delete_permission(self, request: HttpRequest, obj: HourBuilding | None = None): def has_delete_permission(self, request: HttpRequest, obj: HourBuilding | None = None):
return self.has_change_permission(request=request, obj=obj) return self.has_change_permission(request=request, obj=obj)
class HourBuildingInLine(nested_admin.NestedTabularInline): class HourBuildingInLine(nested_admin.NestedTabularInline):
model = HourBuilding model = HourBuilding
inlines = (HourBuildingLegBaseInLine,)
extra = 0 extra = 0
inlines = [HourBuildingLegInline]
fk_name = 'weekpref'
verbose_name_plural = "Hour Building"
max_num = 7 max_num = 7
fk_name = "weekpref"
verbose_name_plural = "Hour Buildings"
formfield_overrides = { formfield_overrides = {
models.CharField: {'widget': TextInput(attrs={'size':'20'})}, models.CharField: {"widget": TextInput(attrs={"size":"20"})},
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':35})}, models.TextField: {"widget": Textarea(attrs={"rows":4, "cols":35})},
} }
# If user is a student deny edit permission for week past the current one
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None):
if hasattr(request.user, 'student') and obj:
current_week = date.today().isocalendar().week
if current_week > obj.week:
return False
return True
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None):
return self.has_change_permission(request=request, obj=obj)

View File

@@ -18,8 +18,8 @@ from ..actions.exportweek import export_selected
from datetime import date from datetime import date
class WeekPreferenceAdmin(nested_admin.NestedModelAdmin): class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
inlines = (TrainingInLIne, HourBuildingInLine,) inlines = (TrainingInLIne, HourBuildingInLine, )
list_display = ("week", "student__surname","student__name", "student__course", "course_color", "student_brief_mix",) list_display = ("week", "student__surname","student__name", "student__course", "course_color", "student_brief_mix",)
list_filter = ("week", "student__course", "student",) list_filter = ("week", "student__course", "student",)
actions = ("export",) actions = ("export",)

View File

@@ -0,0 +1,58 @@
# Generated by Django 5.2.8 on 2025-11-21 11:20
import datetime
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('flightslot', '0017_alter_missionprofile_mtype_alter_weekpreference_week'),
]
operations = [
migrations.CreateModel(
name='HourBuildingLegBase',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('time', models.DurationField(default=datetime.timedelta(seconds=3600))),
('hb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.hourbuilding')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
),
migrations.CreateModel(
name='HourBuildingLegFlight',
fields=[
('hourbuildinglegbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='flightslot.hourbuildinglegbase')),
('departure', models.CharField(default='LILV', max_length=4)),
('destination', models.CharField(default='LILV', max_length=4)),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('flightslot.hourbuildinglegbase',),
),
migrations.CreateModel(
name='HourBuildingLegStop',
fields=[
('hourbuildinglegbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='flightslot.hourbuildinglegbase')),
('location', models.CharField(default='LILV', max_length=4)),
('refuel', models.BooleanField(default=False)),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('flightslot.hourbuildinglegbase',),
),
migrations.DeleteModel(
name='HourBuildingLeg',
),
]

View File

@@ -0,0 +1,37 @@
# Generated by Django 5.2.8 on 2025-11-21 16:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('flightslot', '0018_hourbuildinglegbase_hourbuildinglegflight_and_more'),
]
operations = [
migrations.RemoveField(
model_name='hourbuildinglegstop',
name='location',
),
migrations.AddField(
model_name='hourbuildinglegflight',
name='pax',
field=models.CharField(max_length=16, null=True),
),
migrations.AlterField(
model_name='hourbuildinglegbase',
name='time',
field=models.DurationField(),
),
migrations.AlterField(
model_name='hourbuildinglegflight',
name='departure',
field=models.CharField(max_length=4),
),
migrations.AlterField(
model_name='hourbuildinglegflight',
name='destination',
field=models.CharField(max_length=4),
),
]

View File

@@ -2,10 +2,11 @@ from django.utils.translation import gettext_lazy as _
from django.db import models from django.db import models
from datetime import timedelta from datetime import timedelta
from polymorphic.models import PolymorphicModel
from ..models.weekpref import WeekPreference from ..models.weekpref import WeekPreference
from ..models.aircrafts import AircraftTypes from ..models.aircrafts import AircraftTypes
class HourBuilding(models.Model): class HourBuilding(models.Model):
id = models.BigAutoField( id = models.BigAutoField(
primary_key=True primary_key=True
@@ -66,7 +67,7 @@ class HourBuilding(models.Model):
def __str__(self): def __str__(self):
return f"Hour Building: {self.aircraft}" return f"Hour Building: {self.aircraft}"
class HourBuildingLeg(models.Model): class HourBuildingLegBase(PolymorphicModel):
id = models.BigAutoField( id = models.BigAutoField(
primary_key=True primary_key=True
) )
@@ -76,32 +77,54 @@ class HourBuildingLeg(models.Model):
on_delete=models.CASCADE on_delete=models.CASCADE
) )
time = models.DurationField(
null=False
)
class HourBuildingLegFlight(HourBuildingLegBase):
departure = models.CharField( departure = models.CharField(
null=False, null=False,
blank=False, blank=False,
default="LILV",
max_length=4 max_length=4
) )
destination = models.CharField( destination = models.CharField(
null=False, null=False,
blank=False, blank=False,
default="LILV",
max_length=4 max_length=4
) )
time = models.DurationField( pax = models.CharField(
null=False, null=True,
default = timedelta(hours=1) blank=True,
max_length=16,
verbose_name="Pax (optional)"
) )
stop = models.BooleanField( # Change displayed name in the inline form
class Meta(PolymorphicModel.Meta):
verbose_name = "Flight leg"
verbose_name_plural = "Flight legs"
def __str__(self):
return f"{self.departure} -> {self.destination}"
def save(self, *args, **kwargs):
self.departure = self.departure.capitalize().strip()
self.destination = self.destination.capitalize().strip()
super().save(*args, **kwargs)
class HourBuildingLegStop(HourBuildingLegBase):
refuel = models.BooleanField(
default=False default=False
) )
# Change displayed name in the inline form
class Meta(PolymorphicModel.Meta):
verbose_name = "Stop"
verbose_name_plural = "Stops"
def __str__(self): def __str__(self):
if self.stop: return f"Refuel" if self.refuel else f"No Refuel"
return "Refuelling Stop"
else:
return f"Flight Leg: {self.departure} -> {self.destination}"

View File

@@ -51,24 +51,24 @@ class Student(models.Model):
# Override save method to add user for login upon Student creation # Override save method to add user for login upon Student creation
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
creating = self.pk is None creating: bool = self.pk is None
super().save(*args, **kwargs) super().save(*args, **kwargs)
if creating and not self.user: if creating and not self.user:
username = f"{self.name.lower()}.{self.surname.lower()}" username: str = f"{self.name.lower()}.{self.surname.lower()}"
# Avoid username conflict with progressive number # Avoid username conflict with progressive number
base_username = username base_username = username
counter = 1 counter: int = 1
while User.objects.filter(username=username).exists(): while User.objects.filter(username=username).exists():
username = f"{base_username}{counter}" username = f"{base_username}{counter}"
counter += 1 counter += 1
# Create user # Create user
user = User.objects.create_user( user: User = User.objects.create_user(
first_name=self.name, first_name=self.name,
last_name=self.surname, last_name=self.surname,
username=username, username=username,
email=self.email, email=self.email,
password=self.default_password(), password=self.default_password(),
is_staff=True is_staff=True # allows access to admin page
) )
student_group, _ = Group.objects.get_or_create(name="StudentGroup") student_group, _ = Group.objects.get_or_create(name="StudentGroup")

23
cntmanage/poetry.lock generated
View File

@@ -2,14 +2,14 @@
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.10.0" version = "3.11.0"
description = "ASGI specs, helper code, and adapters" description = "ASGI specs, helper code, and adapters"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734"}, {file = "asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d"},
{file = "asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e"}, {file = "asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4"},
] ]
[package.extras] [package.extras]
@@ -143,6 +143,21 @@ python-monkey-business = ">=1.0.0"
dev = ["Pillow", "black", "dj-database-url", "django-selenosis", "flake8", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"] dev = ["Pillow", "black", "dj-database-url", "django-selenosis", "flake8", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"]
test = ["Pillow", "dj-database-url", "django-selenosis", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"] test = ["Pillow", "dj-database-url", "django-selenosis", "pytest", "pytest-cov", "pytest-django", "pytest-xdist", "selenium"]
[[package]]
name = "django-polymorphic"
version = "4.1.0"
description = "Seamless polymorphic inheritance for Django models"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "django_polymorphic-4.1.0-py3-none-any.whl", hash = "sha256:0ce3984999e103a0d1a434a5c5617f2c7f990dc3d5fb3585ce0fadadf9ff90ea"},
{file = "django_polymorphic-4.1.0.tar.gz", hash = "sha256:4438d95a0aef6c4307cd6c83ead387e1142ce80b65188a931ec2f0dbdd9bfc51"},
]
[package.dependencies]
Django = ">=3.2"
[[package]] [[package]]
name = "et-xmlfile" name = "et-xmlfile"
version = "2.0.0" version = "2.0.0"
@@ -421,4 +436,4 @@ files = [
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "6bf43236f441d8b6bf8d1928910d169d3b29cfa499bb7d09d97ea227f8115658" content-hash = "e932d0af75c888d83fecefaaad1d018c508881a3bfde2ea640a82790e3567855"

View File

@@ -17,6 +17,7 @@ django-import-export = "^4.3.13"
django-colorfield = "^0.14.0" django-colorfield = "^0.14.0"
openpyxl = "^3.1.5" openpyxl = "^3.1.5"
django-admin-action-forms = "^2.2.1" django-admin-action-forms = "^2.2.1"
django-polymorphic = "^4.1.0"
[build-system] [build-system]