Compare commits
8 Commits
airplane-c
...
instructor
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b5319f557 | |||
| 3ee2269d70 | |||
| 5d1686f24b | |||
| 99a8cfe482 | |||
| 2b1042d3a8 | |||
| f06f269568 | |||
| a31798d0b0 | |||
| af62bf843c |
@@ -43,7 +43,8 @@ INSTALLED_APPS = [
|
|||||||
'colorfield',
|
'colorfield',
|
||||||
'import_export',
|
'import_export',
|
||||||
'django_admin_action_forms',
|
'django_admin_action_forms',
|
||||||
'polymorphic'
|
'polymorphic',
|
||||||
|
"phonenumber_field",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Import Export plugin settings
|
# Import Export plugin settings
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ INSTALLED_APPS = [
|
|||||||
'colorfield',
|
'colorfield',
|
||||||
'import_export',
|
'import_export',
|
||||||
'django_admin_action_forms',
|
'django_admin_action_forms',
|
||||||
'polymorphic'
|
'polymorphic',
|
||||||
|
"phonenumber_field",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Import Export plugin settings
|
# Import Export plugin settings
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ from django.contrib import admin
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from flightslot.admin import flightslot_user
|
from flightslot.admin import flightslot_user
|
||||||
|
from flightslot.admin import flightslot_staff
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
#path('', RedirectView.as_view(url='/admin/', permanent=False)),
|
#path('', RedirectView.as_view(url='/admin/', permanent=False)),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('user/', flightslot_user.urls),
|
path('user/', flightslot_user.urls),
|
||||||
|
path('staff/', flightslot_staff.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
|
||||||
]
|
]
|
||||||
|
|||||||
23
cntmanage/flightslot/actions/assign_aircraft.py
Normal file
23
cntmanage/flightslot/actions/assign_aircraft.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from django.db.models.query import QuerySet, Q
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.missions import MissionProfile
|
||||||
|
from ..models.aircrafts import Aircraft, AircraftTypes
|
||||||
|
|
||||||
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
|
def assign_aircraft(queryset: QuerySet[Student] | QuerySet[MissionProfile] | QuerySet[Instructor], data: Dict[str, List[AircraftTypes]]) -> Tuple[int, List[str]]:
|
||||||
|
i: int = 0
|
||||||
|
ac_types: List[AircraftTypes] = data["aircrafts"]
|
||||||
|
ac_query: Q = Q() # Build an or query to select all aircrafts of the specified types
|
||||||
|
for a in ac_types:
|
||||||
|
ac_query |= Q(type=a)
|
||||||
|
aircrafts: QuerySet[Aircraft] = Aircraft.objects.filter(ac_query).all() # Execute query
|
||||||
|
for obj in queryset:
|
||||||
|
obj.aircrafts.clear()
|
||||||
|
for ac in aircrafts:
|
||||||
|
obj.aircrafts.add(ac)
|
||||||
|
obj.save()
|
||||||
|
i += 1
|
||||||
|
return i, [a for a in ac_types]
|
||||||
20
cntmanage/flightslot/actions/assign_profile.py
Normal file
20
cntmanage/flightslot/actions/assign_profile.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from django.db.models.query import QuerySet, Q
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.missions import MissionProfile, MissionTypes
|
||||||
|
|
||||||
|
from typing import List, Dict, Tuple
|
||||||
|
|
||||||
|
def assign_profile(queryset: QuerySet[Instructor], data: Dict[str, List[MissionTypes]]) -> Tuple[int, List[str]]:
|
||||||
|
i: int = 0
|
||||||
|
mix_types: List[MissionTypes] = data["mission_profiles"]
|
||||||
|
mix_query: Q = Q() # Build an or query to select all aircrafts of the specified types
|
||||||
|
for m in mix_types:
|
||||||
|
mix_query |= Q(mtype=m)
|
||||||
|
profiles: QuerySet[MissionProfile] = MissionProfile.objects.filter(mix_query).all() # Execute query
|
||||||
|
for obj in queryset:
|
||||||
|
obj.missions.clear()
|
||||||
|
for ac in profiles:
|
||||||
|
obj.missions.add(ac)
|
||||||
|
obj.save()
|
||||||
|
i += 1
|
||||||
|
return i, [m for m in mix_types]
|
||||||
@@ -6,22 +6,28 @@ from .models.courses import Course
|
|||||||
from .models.students import Student
|
from .models.students import Student
|
||||||
from .models.missions import MissionProfile
|
from .models.missions import MissionProfile
|
||||||
from .models.weekpref import WeekPreference
|
from .models.weekpref import WeekPreference
|
||||||
|
from .models.instructors import Instructor
|
||||||
|
from .models.availabilities import Availability
|
||||||
|
|
||||||
from .admins.aircraft_adm import AircraftAdmin
|
from .admins.aircraft_adm import AircraftAdmin
|
||||||
from .admins.course_adm import CourseAdmin
|
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.weekpref_adm import WeekPreferenceAdmin
|
from .admins.weekpref_adm import WeekPreferenceAdmin
|
||||||
|
from .admins.instructor_admin import InstructorAdmin
|
||||||
|
from .admins.availability_adm import AvailabilityAdmin
|
||||||
|
|
||||||
from django.contrib.admin import AdminSite
|
from django.contrib.admin import AdminSite
|
||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
# User website under /user/ URL
|
##################################
|
||||||
|
# User website under /user/ URL #
|
||||||
|
##################################
|
||||||
class FlightSlotUserSite(AdminSite):
|
class FlightSlotUserSite(AdminSite):
|
||||||
site_header = "Flight Scheduler 🛫"
|
site_header = "Flight Scheduler 🛫"
|
||||||
site_title = "Flight Scheduler 🛫"
|
site_title = "Flight Scheduler 🛫"
|
||||||
index_title = "Welcome to CantorAir Flight Scheduler Portal"
|
index_title = "Welcome to CantorAir Flight Scheduler Student Portal"
|
||||||
|
|
||||||
def get_app_list(self, request: HttpRequest, *args, **kwargs):
|
def get_app_list(self, request: HttpRequest, *args, **kwargs):
|
||||||
app_list = super().get_app_list(request)
|
app_list = super().get_app_list(request)
|
||||||
@@ -35,6 +41,27 @@ class FlightSlotUserSite(AdminSite):
|
|||||||
flightslot_user = FlightSlotUserSite(name="user_site")
|
flightslot_user = FlightSlotUserSite(name="user_site")
|
||||||
flightslot_user.register(WeekPreference, WeekPreferenceAdmin)
|
flightslot_user.register(WeekPreference, WeekPreferenceAdmin)
|
||||||
|
|
||||||
|
##################################
|
||||||
|
# User website under /staff/ URL #
|
||||||
|
##################################
|
||||||
|
class FlightSlotStaffSite(AdminSite):
|
||||||
|
site_header = "Flight Scheduler Staff 🛫"
|
||||||
|
site_title = "Flight Scheduler Staff 🛫"
|
||||||
|
index_title = "Welcome to CantorAir Flight Scheduler Staff Portal"
|
||||||
|
|
||||||
|
def get_app_list(self, request: HttpRequest, *args, **kwargs):
|
||||||
|
app_list = super().get_app_list(request)
|
||||||
|
|
||||||
|
if not request.user.is_superuser:
|
||||||
|
self.enable_nav_sidebar = False
|
||||||
|
|
||||||
|
return app_list
|
||||||
|
|
||||||
|
# Register only user visible models
|
||||||
|
flightslot_staff = FlightSlotUserSite(name="staff_site")
|
||||||
|
flightslot_staff.register(Availability, AvailabilityAdmin)
|
||||||
|
flightslot_staff.register(MissionProfile, MissionProfileAdmin)
|
||||||
|
flightslot_staff.register(Instructor, InstructorAdmin)
|
||||||
|
|
||||||
# Get version for debug purposes
|
# Get version for debug purposes
|
||||||
ver: str = environ.get("VERSION", "dev")
|
ver: str = environ.get("VERSION", "dev")
|
||||||
@@ -48,3 +75,5 @@ 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)
|
||||||
admin.site.register(WeekPreference, WeekPreferenceAdmin)
|
admin.site.register(WeekPreference, WeekPreferenceAdmin)
|
||||||
|
admin.site.register(Instructor, InstructorAdmin)
|
||||||
|
admin.site.register(Availability, AvailabilityAdmin)
|
||||||
|
|||||||
66
cntmanage/flightslot/admins/availability_adm.py
Normal file
66
cntmanage/flightslot/admins/availability_adm.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.availabilities import Availability
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
class AvailabilityForm(forms.ModelForm):
|
||||||
|
model=Availability
|
||||||
|
|
||||||
|
class AvailabilityAdmin(admin.ModelAdmin):
|
||||||
|
model = Availability
|
||||||
|
list_display = ("week", "instructor__surname", "instructor__name", "days_available", "hours")
|
||||||
|
list_filter = ("week", )
|
||||||
|
search_fields = ("instructor__surname","instructor__name", )
|
||||||
|
#actions = ("export", )
|
||||||
|
|
||||||
|
@admin.display(description="Days Available")
|
||||||
|
def days_available(self, obj: Availability) -> SafeText:
|
||||||
|
if not obj:
|
||||||
|
return SafeText("")
|
||||||
|
days: List[str | None] = [
|
||||||
|
"Mon" if obj.monday else None,
|
||||||
|
"Tue" if obj.tuesday else None,
|
||||||
|
"Wed" if obj.wednesday else None,
|
||||||
|
"Thu" if obj.thursday else None,
|
||||||
|
"Fri" if obj.friday else None,
|
||||||
|
"Sat" if obj.saturday else None,
|
||||||
|
"Sun" if obj.sunday else None,
|
||||||
|
]
|
||||||
|
return SafeText("/".join(d if d else "" for d in days))
|
||||||
|
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
||||||
|
return super().get_queryset(request).order_by("-week", "instructor__surname", "instructor__name")
|
||||||
|
|
||||||
|
def get_form(self, request: HttpRequest, obj: Availability | None = None, change: bool = False, **kwargs: Any) -> AvailabilityForm:
|
||||||
|
form: AvailabilityForm = super().get_form(request, obj, change, **kwargs)
|
||||||
|
|
||||||
|
if change: # if is only a form change do not set default values and return form
|
||||||
|
return form
|
||||||
|
|
||||||
|
# If form contains the week field
|
||||||
|
current_week = date.today().isocalendar().week
|
||||||
|
if "week" in form.base_fields:
|
||||||
|
# Set default value as current week
|
||||||
|
form.base_fields["week"].initial = current_week
|
||||||
|
|
||||||
|
# If student is current user making request
|
||||||
|
if hasattr(request.user, "instructor"):
|
||||||
|
instructor: Instructor = request.user.instructor
|
||||||
|
if "instructor" in form.base_fields:
|
||||||
|
form.base_fields["instructor"].initial = instructor
|
||||||
|
form.base_fields["instructor"].disabled = True
|
||||||
|
return form
|
||||||
|
|
||||||
|
# Imposta automaticamente l'istruttore se non è già valorizzato
|
||||||
|
def save_model(self, request: HttpRequest, obj: Availability, form: AvailabilityForm, change: bool):
|
||||||
|
if hasattr(request.user, "instructor") and not obj.instructor_id:
|
||||||
|
obj.instructor = request.user.instructor
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
@@ -1,12 +1,24 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from ..models.students import Student
|
||||||
from ..models.courses import Course
|
from ..models.courses import Course
|
||||||
from ..custom.colortag import course_color
|
from ..custom.colortag import course_color
|
||||||
|
|
||||||
class CourseAdmin(admin.ModelAdmin):
|
class CourseAdmin(admin.ModelAdmin):
|
||||||
list_display = ("ctype", "cnumber","color_display", "year")
|
list_display = ("ctype", "cnumber","color_display", "course_students", "year")
|
||||||
list_filter = ("ctype", "year")
|
list_filter = ("ctype", "year")
|
||||||
|
|
||||||
|
def get_queryset(self, request: HttpRequest) -> QuerySet:
|
||||||
|
return super().get_queryset(request).order_by("ctype", "cnumber")
|
||||||
|
|
||||||
|
@admin.display(description="Student Number")
|
||||||
|
def course_students(self, obj: Course) -> SafeText:
|
||||||
|
if not obj.pk:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText(f"{Student.objects.filter(course = obj.id).count()}")
|
||||||
|
|
||||||
# Dinamically add color_display property to show a colored dot
|
# Dinamically add color_display property to show a colored dot
|
||||||
@admin.display(description="Color")
|
@admin.display(description="Color")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from durationwidget.widgets import TimeDurationWidget
|
|||||||
from ..models.hourbuildings import HourBuilding, HourBuildingLegBase, HourBuildingLegFlight, HourBuildingLegStop
|
from ..models.hourbuildings import HourBuilding, HourBuildingLegBase, HourBuildingLegFlight, HourBuildingLegStop
|
||||||
from ..models.weekpref import WeekPreference
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
from datetime import date
|
from ..custom.student_permissions import has_edit_permission
|
||||||
|
|
||||||
class HourBuildingLegFlightForm(forms.ModelForm):
|
class HourBuildingLegFlightForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -74,11 +74,7 @@ class HourBuildingInLine(nested_admin.NestedTabularInline):
|
|||||||
|
|
||||||
# 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: WeekPreference | None = None) -> bool:
|
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
if hasattr(request.user, 'student') and obj:
|
return has_edit_permission(request=request, obj=obj)
|
||||||
current_week: int = date.today().isocalendar().week
|
|
||||||
if current_week > obj.week:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
return self.has_change_permission(request=request, obj=obj)
|
return self.has_change_permission(request=request, obj=obj)
|
||||||
|
|||||||
95
cntmanage/flightslot/admins/instructor_admin.py
Normal file
95
cntmanage/flightslot/admins/instructor_admin.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
from django.forms import TypedMultipleChoiceField
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.contrib import admin, messages
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||||
|
|
||||||
|
from import_export import fields
|
||||||
|
from import_export.admin import ImportMixin
|
||||||
|
from import_export.tmp_storages import CacheStorage
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
from ..models.aircrafts import AircraftTypes
|
||||||
|
from ..models.missions import MissionTypes
|
||||||
|
|
||||||
|
from ..actions.assign_profile import assign_profile
|
||||||
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
# Resource Class for Instructor data import
|
||||||
|
class InstructorResource(ModelResource):
|
||||||
|
surname = fields.Field(attribute="surname", column_name="surname")
|
||||||
|
name = fields.Field(attribute="name", column_name="name")
|
||||||
|
email = fields.Field(attribute="email", column_name="email")
|
||||||
|
phone = fields.Field(attribute="phone", column_name="phone")
|
||||||
|
|
||||||
|
# Cleanup fields before entering
|
||||||
|
def before_import_row(self, row: Dict[str, str], **kwargs) -> None:
|
||||||
|
row["name"] = SafeText("-".join(c.capitalize() for c in row["name"].split(" ")).strip())
|
||||||
|
row["surname"] = SafeText("-".join(c.capitalize() for c in row["surname"].split(" ")).strip())
|
||||||
|
row["phone"] = SafeText(row["phone"].replace(" ",""))
|
||||||
|
row["email"] = SafeText(row["email"].lower().strip())
|
||||||
|
return super().before_import_row(row, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Instructor
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
fields = ("surname", "name", "email", "phone", )
|
||||||
|
import_id_fields = ("surname", "name", )
|
||||||
|
|
||||||
|
# Form class to assing aircrafts to instructors
|
||||||
|
class AssignMissionForm(AdminActionForm):
|
||||||
|
mission_profiles = TypedMultipleChoiceField(choices=MissionTypes)
|
||||||
|
|
||||||
|
# Form class to assing aircrafts to instructors
|
||||||
|
class AssignAircraftForm(AdminActionForm):
|
||||||
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
|
class InstructorAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
|
model = Instructor
|
||||||
|
list_display = ("surname", "name", "email", "phone", "assigned_profiles", "assigned_aircrafts", "active", )
|
||||||
|
search_fields = ("surname", "name", "phone", "email", )
|
||||||
|
readonly_fields = ("username", "password", )
|
||||||
|
actions = ("assign_aircraft", "assign_profile", )
|
||||||
|
resource_classes = [InstructorResource]
|
||||||
|
tmp_storage_class = CacheStorage
|
||||||
|
skip_admin_log = True
|
||||||
|
|
||||||
|
@admin.display(description="Password")
|
||||||
|
def password(self, obj: Instructor) -> SafeText:
|
||||||
|
return SafeText(obj.default_password())
|
||||||
|
|
||||||
|
@admin.display(description="Username")
|
||||||
|
def username(self, obj: Instructor) -> SafeText:
|
||||||
|
return SafeText(obj.default_username())
|
||||||
|
|
||||||
|
@admin.display(description="Assigned Profiles")
|
||||||
|
def assigned_profiles(self, obj: Instructor) -> SafeText:
|
||||||
|
if not obj.aircrafts:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText("/".join(mix.mtype for mix in obj.missions.distinct("mtype").order_by("mtype").all()))
|
||||||
|
|
||||||
|
@admin.display(description="Assigned Aircrafts")
|
||||||
|
def assigned_aircrafts(self, obj: Instructor) -> SafeText:
|
||||||
|
if not obj.aircrafts:
|
||||||
|
return SafeText("")
|
||||||
|
return SafeText("/".join(ac.type for ac in obj.aircrafts.distinct("type").order_by("type").all()))
|
||||||
|
|
||||||
|
@action_with_form(AssignAircraftForm, description="Assign Aircraft Type")
|
||||||
|
def assign_aircraft(self, request: HttpRequest, queryset: QuerySet[Instructor], data: Dict[str, List[AircraftTypes]]):
|
||||||
|
i: int
|
||||||
|
ac_types: List[str]
|
||||||
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
|
messages.success(request, f"{i} Instructors updated to {ac_types}")
|
||||||
|
|
||||||
|
@action_with_form(AssignMissionForm, description="Assign Mission Type")
|
||||||
|
def assign_profile(self, request: HttpRequest, queryset: QuerySet[Instructor], data: Dict[str, List[MissionTypes]]):
|
||||||
|
i: int
|
||||||
|
mix_types: List[str]
|
||||||
|
i, mix_types = assign_profile(queryset=queryset, data=data)
|
||||||
|
messages.success(request, f"{i} Instructors updated to {mix_types}")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.forms import ModelMultipleChoiceField
|
from django.forms import TypedMultipleChoiceField
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.db.models.query import QuerySet, Q
|
from django.db.models.query import QuerySet
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||||
@@ -11,11 +11,13 @@ from import_export.admin import ImportMixin
|
|||||||
from import_export.tmp_storages import CacheStorage
|
from import_export.tmp_storages import CacheStorage
|
||||||
from import_export.resources import ModelResource
|
from import_export.resources import ModelResource
|
||||||
|
|
||||||
from ..models.aircrafts import Aircraft
|
from ..models.aircrafts import AircraftTypes
|
||||||
from ..models.missions import MissionProfile
|
from ..models.missions import MissionProfile
|
||||||
|
|
||||||
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
# Resource Class for Student data import
|
# Resource Class for Student data import
|
||||||
class MissionProfileResource(ModelResource):
|
class MissionProfileResource(ModelResource):
|
||||||
@@ -40,30 +42,25 @@ class MissionProfileResource(ModelResource):
|
|||||||
|
|
||||||
# Form class to assing aircrafts to students
|
# Form class to assing aircrafts to students
|
||||||
class ChangeAircraftForm(AdminActionForm):
|
class ChangeAircraftForm(AdminActionForm):
|
||||||
aircrafts = ModelMultipleChoiceField(queryset=Aircraft.objects.distinct('type').all())
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
class MissionProfileAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
class MissionProfileAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
list_display = ("mtype", "mnum", "assigned_aircrafts", "duration", "notes", )
|
list_display = ("mtype", "mnum", "assigned_aircrafts", "duration", "notes", )
|
||||||
list_filter = ("mtype", )
|
list_filter = ("mtype", )
|
||||||
actions = ("assign_aircraft", )
|
actions = ("assign_aircraft", )
|
||||||
|
resource_classes = [MissionProfileResource]
|
||||||
tmp_storage_class = CacheStorage
|
tmp_storage_class = CacheStorage
|
||||||
skip_admin_log = True
|
skip_admin_log = True
|
||||||
|
|
||||||
@action_with_form(ChangeAircraftForm, description="Assign Aircraft")
|
def get_queryset(self, request: HttpRequest) -> QuerySet[MissionProfile]:
|
||||||
def assign_aircraft(self, request: HttpRequest, queryset: QuerySet[MissionProfile], data: Dict[str, QuerySet[Aircraft]]):
|
return super().get_queryset(request).order_by("mtype", "mnum")
|
||||||
ac_types = [t.type for t in data["aircrafts"]]
|
|
||||||
ac_query: Q = Q() # Build an or query to select all aircrafts of the specified types
|
@action_with_form(ChangeAircraftForm, description="Assign Aircraft Type")
|
||||||
for a in ac_types:
|
def assign_aircraft(self, request: HttpRequest, queryset: QuerySet[MissionProfile], data: Dict[str, List[AircraftTypes]]):
|
||||||
ac_query |= Q(type=a)
|
i: int
|
||||||
aircrafts: QuerySet[Aircraft] = Aircraft.objects.filter(ac_query).all() # Execute query
|
ac_types: List[str]
|
||||||
i: int = 0
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
for mix in queryset:
|
messages.success(request, f"{i} Missions updated to {ac_types}")
|
||||||
mix.aircrafts.clear()
|
|
||||||
for ac in aircrafts:
|
|
||||||
mix.aircrafts.add(ac)
|
|
||||||
mix.save()
|
|
||||||
i += 1
|
|
||||||
messages.success(request, f"{i} Students updated to {ac_types}")
|
|
||||||
|
|
||||||
@admin.display(description="Assigned Aircrafts")
|
@admin.display(description="Assigned Aircrafts")
|
||||||
def assigned_aircrafts(self, obj: MissionProfile) -> SafeText:
|
def assigned_aircrafts(self, obj: MissionProfile) -> SafeText:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.forms import ModelChoiceField, TypedMultipleChoiceField, ModelMultipleChoiceField
|
from django.forms import ModelChoiceField, TypedMultipleChoiceField
|
||||||
from django.db.models.query import QuerySet, Q
|
from django.db.models.query import QuerySet
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
@@ -12,13 +12,15 @@ from import_export.forms import ConfirmImportForm, ImportForm
|
|||||||
|
|
||||||
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||||
|
|
||||||
from ..models.aircrafts import Aircraft, AircraftTypes
|
from ..models.aircrafts import AircraftTypes
|
||||||
from ..models.courses import Course
|
from ..models.courses import Course
|
||||||
from ..models.students import Student
|
from ..models.students import Student
|
||||||
|
|
||||||
|
from ..actions.assign_aircraft import assign_aircraft
|
||||||
|
|
||||||
from ..custom.colortag import course_color
|
from ..custom.colortag import course_color
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
# Custom import form to select a course for student input
|
# Custom import form to select a course for student input
|
||||||
class StudentCustomConfirmImportForm(ConfirmImportForm):
|
class StudentCustomConfirmImportForm(ConfirmImportForm):
|
||||||
@@ -56,18 +58,18 @@ class StudentResource(ModelResource):
|
|||||||
|
|
||||||
# Form Class for Student course change
|
# Form Class for Student course change
|
||||||
class ChangeCourseForm(AdminActionForm):
|
class ChangeCourseForm(AdminActionForm):
|
||||||
course = TypedMultipleChoiceField(choices=AircraftTypes)
|
course = ModelChoiceField(queryset=Course.objects.all().order_by("ctype", "-cnumber"))
|
||||||
|
|
||||||
# Form class to assing aircrafts to students
|
# Form class to assing aircrafts to students
|
||||||
class ChangeAircraftForm(AdminActionForm):
|
class ChangeAircraftForm(AdminActionForm):
|
||||||
aircrafts = ModelMultipleChoiceField(queryset=Aircraft.objects.distinct('type').all())
|
aircrafts = TypedMultipleChoiceField(choices=AircraftTypes)
|
||||||
|
|
||||||
class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
model = Student
|
model = Student
|
||||||
list_display = ("surname", "name", "course", "course_color", "email", "phone", "username", "password", "active", )
|
list_display = ("surname", "name", "course", "course_color", "email", "phone", "username", "password", "active", )
|
||||||
list_filter = ("course", "active", )
|
list_filter = ("course", "active", )
|
||||||
search_fields = ("surname", "name", "phone", "email", )
|
search_fields = ("surname", "name", "phone", "email", )
|
||||||
actions = ("change_course", "disable_students", "change_aircraft", )
|
actions = ("change_course", "deactivate_students", "change_aircraft", )
|
||||||
resource_classes = [StudentResource]
|
resource_classes = [StudentResource]
|
||||||
confirm_form_class = StudentCustomConfirmImportForm
|
confirm_form_class = StudentCustomConfirmImportForm
|
||||||
tmp_storage_class = CacheStorage
|
tmp_storage_class = CacheStorage
|
||||||
@@ -88,7 +90,7 @@ class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
|||||||
return SafeText(obj.default_username())
|
return SafeText(obj.default_username())
|
||||||
|
|
||||||
@admin.action(description="Deactivate Students")
|
@admin.action(description="Deactivate Students")
|
||||||
def disable_students(self, request: HttpRequest, queryset: QuerySet[Student]):
|
def deactivate_students(self, request: HttpRequest, queryset: QuerySet[Student]):
|
||||||
for q in queryset.all():
|
for q in queryset.all():
|
||||||
if q.user:
|
if q.user:
|
||||||
q.user.is_staff = False
|
q.user.is_staff = False
|
||||||
@@ -102,20 +104,11 @@ class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
|||||||
count: int = queryset.update(course=course)
|
count: int = queryset.update(course=course)
|
||||||
messages.success(request, f"{count} students updated to {course}")
|
messages.success(request, f"{count} students updated to {course}")
|
||||||
|
|
||||||
@action_with_form(ChangeAircraftForm, description="Assign Aircraft")
|
@action_with_form(ChangeAircraftForm, description="Assign Aircraft Type")
|
||||||
def change_aircraft(self, request: HttpRequest, queryset: QuerySet[Student], data: Dict[str, QuerySet[Aircraft]]):
|
def change_aircraft(self, request: HttpRequest, queryset: QuerySet[Student], data: Dict[str, List[AircraftTypes]]):
|
||||||
ac_types = [t.type for t in data["aircrafts"]]
|
i: int
|
||||||
ac_query: Q = Q() # Build an or query to select all aircrafts of the specified types
|
ac_types: List[str]
|
||||||
for a in ac_types:
|
i, ac_types = assign_aircraft(queryset=queryset, data=data)
|
||||||
ac_query |= Q(type=a)
|
|
||||||
aircrafts: QuerySet[Aircraft] = Aircraft.objects.filter(ac_query).all() # Execute query
|
|
||||||
i: int = 0
|
|
||||||
for student in queryset:
|
|
||||||
student.aircrafts.clear()
|
|
||||||
for ac in aircrafts:
|
|
||||||
student.aircrafts.add(ac)
|
|
||||||
student.save()
|
|
||||||
i += 1
|
|
||||||
messages.success(request, f"{i} Students updated to {ac_types}")
|
messages.success(request, f"{i} Students updated to {ac_types}")
|
||||||
|
|
||||||
# Return the initial form for import confirmations, request course to user
|
# Return the initial form for import confirmations, request course to user
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from django.http import HttpRequest
|
|||||||
from ..models.missions import Training
|
from ..models.missions import Training
|
||||||
from ..models.weekpref import WeekPreference
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from ..custom.student_permissions import has_edit_permission
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
class TrainingForm(forms.ModelForm):
|
class TrainingForm(forms.ModelForm):
|
||||||
@@ -27,11 +29,7 @@ class TrainingInLIne(nested_admin.NestedTabularInline):
|
|||||||
|
|
||||||
# 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: WeekPreference | None = None) -> bool:
|
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
if hasattr(request.user, 'student') and obj:
|
return has_edit_permission(request=request, obj=obj)
|
||||||
current_week: int = date.today().isocalendar().week
|
|
||||||
if current_week > obj.week:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
return self.has_change_permission(request=request, obj=obj)
|
return self.has_change_permission(request=request, obj=obj)
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import nested_admin
|
|||||||
|
|
||||||
from django.forms import Form
|
from django.forms import Form
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.utils.translation import ngettext
|
from django.utils.translation import ngettext
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
|
|
||||||
|
from ..models.students import Student
|
||||||
from ..models.missions import Training
|
from ..models.missions import Training
|
||||||
from ..models.weekpref import WeekPreference
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ from .training_adm import TrainingInLIne
|
|||||||
from .hourbuilding_adm import HourBuildingInLine
|
from .hourbuilding_adm import HourBuildingInLine
|
||||||
|
|
||||||
from ..custom.colortag import course_color
|
from ..custom.colortag import course_color
|
||||||
|
from ..custom.student_permissions import has_edit_permission
|
||||||
from ..actions.exportweek import export_selected
|
from ..actions.exportweek import export_selected
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@@ -62,7 +63,7 @@ class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
|||||||
|
|
||||||
# If a user is registered as student show only their preferences
|
# If a user is registered as student show only their preferences
|
||||||
def get_queryset(self, request: HttpRequest) -> QuerySet[WeekPreference]:
|
def get_queryset(self, request: HttpRequest) -> QuerySet[WeekPreference]:
|
||||||
qs = super().get_queryset(request)
|
qs = super().get_queryset(request).order_by("-week", "-student__course", "student__surname", "student__name")
|
||||||
if hasattr(request.user, "student"):
|
if hasattr(request.user, "student"):
|
||||||
return qs.filter(student=request.user.student)
|
return qs.filter(student=request.user.student)
|
||||||
# If admin show everything
|
# If admin show everything
|
||||||
@@ -79,7 +80,7 @@ class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
|||||||
|
|
||||||
# If student is current user making request
|
# If student is current user making request
|
||||||
if hasattr(request.user, "student"):
|
if hasattr(request.user, "student"):
|
||||||
student = request.user.student
|
student: Student = request.user.student
|
||||||
if "student" in form.base_fields:
|
if "student" in form.base_fields:
|
||||||
form.base_fields["student"].initial = student
|
form.base_fields["student"].initial = student
|
||||||
form.base_fields["student"].disabled = True
|
form.base_fields["student"].disabled = True
|
||||||
@@ -88,26 +89,21 @@ class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
|||||||
|
|
||||||
# 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: WeekPreference | None = None) -> bool:
|
def has_change_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
if hasattr(request.user, "student") and obj:
|
return has_edit_permission(request=request, obj=obj)
|
||||||
current_week = date.today().isocalendar().week
|
|
||||||
if current_week > obj.week:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 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_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
def has_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
return self.has_change_permission(request, obj)
|
return not obj and self.has_change_permission(request, obj)
|
||||||
|
|
||||||
# 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_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None)-> bool:
|
def has_delete_permission(self, request: HttpRequest, obj: WeekPreference | None = None)-> bool:
|
||||||
return self.has_change_permission(request, obj)
|
return self.has_change_permission(request, obj)
|
||||||
|
|
||||||
def changeform_view(self, request: HttpRequest, object_id: int | None = None, form_url: str = "", extra_context=None):
|
def changeform_view(self, request: HttpRequest, object_id: int | None = None, form_url: str = "", extra_context = None):
|
||||||
extra_context = extra_context or {}
|
extra_context = extra_context or {}
|
||||||
if hasattr(request.user, "student") and object_id:
|
if hasattr(request.user, "student") and object_id:
|
||||||
current_week = date.today().isocalendar().week
|
|
||||||
weekpref = WeekPreference.objects.get(id=object_id)
|
weekpref = WeekPreference.objects.get(id=object_id)
|
||||||
if current_week > weekpref.week:
|
if not has_edit_permission(request=request, obj=weekpref):
|
||||||
extra_context["show_save"] = False
|
extra_context["show_save"] = False
|
||||||
extra_context["show_save_and_continue"] = False
|
extra_context["show_save_and_continue"] = False
|
||||||
extra_context["show_save_and_add_another"] = False
|
extra_context["show_save_and_add_another"] = False
|
||||||
|
|||||||
17
cntmanage/flightslot/custom/student_permissions.py
Normal file
17
cntmanage/flightslot/custom/student_permissions.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# allow add, modify, delete depending on a set of requirements
|
||||||
|
def has_edit_permission(request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
|
if hasattr(request.user, 'student'):
|
||||||
|
student: Student = request.user.student
|
||||||
|
if not student.active:
|
||||||
|
return False
|
||||||
|
current_week: int = date.today().isocalendar().week
|
||||||
|
if obj and (current_week > obj.week or not student.active):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
@@ -7,8 +7,10 @@ class RedirectNonSuperuserFromAdminMiddleware:
|
|||||||
self.get_response = get_response
|
self.get_response = get_response
|
||||||
|
|
||||||
def __call__(self, request: HttpRequest):
|
def __call__(self, request: HttpRequest):
|
||||||
# Se l'utente è loggato, non è superuser e prova ad andare in /admin/...
|
# Se l'utente è loggato, non è superuser e prova ad andare in /admin/... o qualsiasi altro path
|
||||||
if hasattr(request, "user") and not request.user.is_superuser:
|
if hasattr(request, "user") and not request.user.is_superuser:
|
||||||
if "/admin/" in request.path:
|
if hasattr(request.user, "student") and not "/user/" in request.path:
|
||||||
return redirect("/user/") # redirect automatico
|
return redirect("/user/") # redirect automatico
|
||||||
|
elif hasattr(request.user, "instructor") and not "/staff/" in request.path:
|
||||||
|
return redirect("/staff/") # redirect automatico
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-01 12:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0024_alter_missionprofile_mtype'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='aircraft',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('PA34', 'Piper PA34'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010'), ('CP10', 'Cap 10'), ('FSTD', 'Alsim ALX40')], max_length=4),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hourbuilding',
|
||||||
|
name='aircraft',
|
||||||
|
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('PA34', 'Piper PA34'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010'), ('CP10', 'Cap 10'), ('FSTD', 'Alsim ALX40')]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='week',
|
||||||
|
field=models.PositiveSmallIntegerField(auto_created=True, db_default=49, db_index=True, verbose_name='Week Number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-01 17:27
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import phonenumber_field.modelfields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0025_alter_aircraft_type_alter_hourbuilding_aircraft_and_more'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='student',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(db_index=True, max_length=254, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='student',
|
||||||
|
name='phone',
|
||||||
|
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Instructor',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('email', models.EmailField(db_index=True, max_length=254)),
|
||||||
|
('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None, unique=True)),
|
||||||
|
('name', models.CharField(max_length=32)),
|
||||||
|
('surname', models.CharField(max_length=32)),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('aircrafts', models.ManyToManyField(to='flightslot.aircraft')),
|
||||||
|
('missions', models.ManyToManyField(to='flightslot.missionprofile')),
|
||||||
|
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-02 10:02
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0026_alter_student_email_alter_student_phone_instructor'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weekpreference',
|
||||||
|
name='student',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.student', verbose_name='Student Selection'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Availability',
|
||||||
|
fields=[
|
||||||
|
('week', models.PositiveSmallIntegerField(auto_created=True, db_default=49, db_index=True, verbose_name='Week Number')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('monday', models.BooleanField(default=True)),
|
||||||
|
('tuesday', models.BooleanField(default=True)),
|
||||||
|
('wednesday', models.BooleanField(default=True)),
|
||||||
|
('thursday', models.BooleanField(default=True)),
|
||||||
|
('friday', models.BooleanField(default=True)),
|
||||||
|
('saturday', models.BooleanField(default=True)),
|
||||||
|
('sunday', models.BooleanField(default=True)),
|
||||||
|
('hours', models.DurationField(null=True, verbose_name='Available hours')),
|
||||||
|
('notes', models.TextField(blank=True, max_length=140, null=True)),
|
||||||
|
('instructor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.instructor', verbose_name='Instructor Selection')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.8 on 2025-12-02 10:59
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('flightslot', '0027_alter_weekpreference_student_availability'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='availability',
|
||||||
|
options={'verbose_name': 'Instructor Availability', 'verbose_name_plural': 'Instructor Availabilities'},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -8,6 +8,7 @@ class AircraftTypes(models.TextChoices):
|
|||||||
PA34 = "PA34", _("Piper PA34")
|
PA34 = "PA34", _("Piper PA34")
|
||||||
C182 = "C182", _("Cessna 182Q")
|
C182 = "C182", _("Cessna 182Q")
|
||||||
P210 = "TWEN", _("Tecnam P2010")
|
P210 = "TWEN", _("Tecnam P2010")
|
||||||
|
CP10 = "CP10", _("Cap 10")
|
||||||
ALX40 = "FSTD", _("Alsim ALX40")
|
ALX40 = "FSTD", _("Alsim ALX40")
|
||||||
|
|
||||||
class Aircraft(models.Model):
|
class Aircraft(models.Model):
|
||||||
|
|||||||
79
cntmanage/flightslot/models/availabilities.py
Normal file
79
cntmanage/flightslot/models/availabilities.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from django.db import models
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from ..models.instructors import Instructor
|
||||||
|
|
||||||
|
class Availability(models.Model):
|
||||||
|
id = models.BigAutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
week = models.PositiveSmallIntegerField(
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
db_default=date.today().isocalendar().week,
|
||||||
|
auto_created=True,
|
||||||
|
verbose_name="Week Number"
|
||||||
|
)
|
||||||
|
|
||||||
|
instructor = models.ForeignKey(
|
||||||
|
Instructor,
|
||||||
|
null=False,
|
||||||
|
db_index=True,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name="Instructor Selection"
|
||||||
|
)
|
||||||
|
|
||||||
|
monday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
tuesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
wednesday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
thursday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
friday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
saturday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
sunday = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
null=False
|
||||||
|
)
|
||||||
|
|
||||||
|
hours = models.DurationField(
|
||||||
|
null=True,
|
||||||
|
verbose_name="Available hours"
|
||||||
|
)
|
||||||
|
|
||||||
|
notes = models.TextField(
|
||||||
|
max_length=140,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta():
|
||||||
|
verbose_name = "Instructor Availability"
|
||||||
|
verbose_name_plural = "Instructor Availabilities"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Week {self.week} - {self.instructor.surname} {self.instructor.name[0]}."
|
||||||
|
|
||||||
99
cntmanage/flightslot/models/instructors.py
Normal file
99
cntmanage/flightslot/models/instructors.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
from phonenumber_field import modelfields
|
||||||
|
|
||||||
|
from ..models.missions import MissionProfile
|
||||||
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
|
class Instructor(models.Model):
|
||||||
|
id = models.AutoField(
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
email = models.EmailField(
|
||||||
|
null=False,
|
||||||
|
db_index=True
|
||||||
|
)
|
||||||
|
|
||||||
|
phone = modelfields.PhoneNumberField(
|
||||||
|
null=True,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
name = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
|
|
||||||
|
surname = models.CharField(
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
max_length=32
|
||||||
|
)
|
||||||
|
|
||||||
|
active = models.BooleanField(
|
||||||
|
null=False,
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
aircrafts = models.ManyToManyField(
|
||||||
|
Aircraft
|
||||||
|
)
|
||||||
|
|
||||||
|
missions = models.ManyToManyField(
|
||||||
|
MissionProfile
|
||||||
|
)
|
||||||
|
|
||||||
|
user = models.OneToOneField(
|
||||||
|
User,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def default_password(self) -> str: # Maximum 4 digits for passowrd
|
||||||
|
if self.pk:
|
||||||
|
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id % 10000}"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def default_username(self) -> str:
|
||||||
|
if self.pk and self.user:
|
||||||
|
return self.user.username
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Override save method to add user for login upon Student creation
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
creating: bool = self.pk is None
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
if creating and not self.user:
|
||||||
|
username: str = f"{self.name.lower()}.{self.surname.lower()}"
|
||||||
|
# Avoid username conflict with progressive number
|
||||||
|
base_username = username
|
||||||
|
counter: int = 1
|
||||||
|
while User.objects.filter(username=username).exists():
|
||||||
|
username = f"{base_username}{counter}"
|
||||||
|
counter += 1
|
||||||
|
# Create user
|
||||||
|
user: User = User.objects.create_user(
|
||||||
|
first_name=self.name.capitalize(),
|
||||||
|
last_name=self.surname.capitalize(),
|
||||||
|
username=username,
|
||||||
|
email=self.email,
|
||||||
|
password=self.default_password(),
|
||||||
|
is_staff=True # allows access to admin page
|
||||||
|
)
|
||||||
|
|
||||||
|
instructor_group, _ = Group.objects.get_or_create(name="InstructorGroup")
|
||||||
|
user.groups.add(instructor_group)
|
||||||
|
self.user = user
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.pk:
|
||||||
|
return f"{self.surname} {self.name[0]}."
|
||||||
|
else:
|
||||||
|
return "New Instructor"
|
||||||
@@ -5,7 +5,7 @@ from datetime import timedelta
|
|||||||
from ..models.weekpref import WeekPreference
|
from ..models.weekpref import WeekPreference
|
||||||
from ..models.aircrafts import Aircraft
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
class MissionType(models.TextChoices):
|
class MissionTypes(models.TextChoices):
|
||||||
OTHER = "OTHER", _("OTHER")
|
OTHER = "OTHER", _("OTHER")
|
||||||
CHK = "CHK", _("CHK_6M")
|
CHK = "CHK", _("CHK_6M")
|
||||||
PPL = "PPL", _("PPL")
|
PPL = "PPL", _("PPL")
|
||||||
@@ -24,8 +24,8 @@ class MissionProfile(models.Model):
|
|||||||
|
|
||||||
mtype = models.CharField(
|
mtype = models.CharField(
|
||||||
null=False,
|
null=False,
|
||||||
default=MissionType.PPL,
|
default=MissionTypes.PPL,
|
||||||
choices=MissionType,
|
choices=MissionTypes,
|
||||||
verbose_name="Mission Type"
|
verbose_name="Mission Type"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User, Group
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
|
from phonenumber_field import modelfields
|
||||||
|
|
||||||
from ..models.courses import Course
|
from ..models.courses import Course
|
||||||
from ..models.aircrafts import Aircraft
|
from ..models.aircrafts import Aircraft
|
||||||
|
|
||||||
@@ -11,12 +13,13 @@ class Student(models.Model):
|
|||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
null=False,
|
null=False,
|
||||||
db_index=True
|
db_index=True,
|
||||||
|
unique=True
|
||||||
)
|
)
|
||||||
|
|
||||||
phone = models.CharField(
|
phone = modelfields.PhoneNumberField(
|
||||||
null=True,
|
null=True,
|
||||||
max_length=16
|
unique=True
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
@@ -52,7 +55,10 @@ class Student(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def default_password(self) -> str: # Maximum 4 digits for passowrd
|
def default_password(self) -> str: # Maximum 4 digits for passowrd
|
||||||
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id % 10000}"
|
if self.pk:
|
||||||
|
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id % 10000}"
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
def default_username(self) -> str:
|
def default_username(self) -> str:
|
||||||
if self.pk and self.user:
|
if self.pk and self.user:
|
||||||
@@ -74,8 +80,8 @@ class Student(models.Model):
|
|||||||
counter += 1
|
counter += 1
|
||||||
# Create user
|
# Create user
|
||||||
user: User = User.objects.create_user(
|
user: User = User.objects.create_user(
|
||||||
first_name=self.name,
|
first_name=self.name.capitalize(),
|
||||||
last_name=self.surname,
|
last_name=self.surname.capitalize(),
|
||||||
username=username,
|
username=username,
|
||||||
email=self.email,
|
email=self.email,
|
||||||
password=self.default_password(),
|
password=self.default_password(),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class WeekPreference(models.Model):
|
|||||||
Student,
|
Student,
|
||||||
null=False,
|
null=False,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
on_delete=models.DO_NOTHING,
|
on_delete=models.CASCADE,
|
||||||
verbose_name="Student Selection"
|
verbose_name="Student Selection"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
34
cntmanage/poetry.lock
generated
34
cntmanage/poetry.lock
generated
@@ -143,6 +143,26 @@ 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-phonenumber-field"
|
||||||
|
version = "8.4.0"
|
||||||
|
description = "An international phone number field for django models."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_phonenumber_field-8.4.0-py3-none-any.whl", hash = "sha256:7a1cb3a6456edb54d879f11ffa0acb227ded08c93b587035d0f28093f0e46511"},
|
||||||
|
{file = "django_phonenumber_field-8.4.0.tar.gz", hash = "sha256:2b83e843dac35eec6a69880a166487235b737a71a1e38c9a52e5ad67d6996083"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=4.2"
|
||||||
|
phonenumberslite = {version = ">=7.0.2", optional = true, markers = "extra == \"phonenumberslite\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
phonenumbers = ["phonenumbers (>=7.0.2)"]
|
||||||
|
phonenumberslite = ["phonenumberslite (>=7.0.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-polymorphic"
|
name = "django-polymorphic"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@@ -185,6 +205,18 @@ files = [
|
|||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
et-xmlfile = "*"
|
et-xmlfile = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phonenumberslite"
|
||||||
|
version = "9.0.19"
|
||||||
|
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "phonenumberslite-9.0.19-py2.py3-none-any.whl", hash = "sha256:92a2426808e7d40b4acf36c97dcc436747807419c5dbc035330df28c13d41c0f"},
|
||||||
|
{file = "phonenumberslite-9.0.19.tar.gz", hash = "sha256:3794fcec9d2a6510a806187de750853c73ea5dabaac4ecd7fa36e79f869b3c2e"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.0.0"
|
version = "12.0.0"
|
||||||
@@ -436,4 +468,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "e932d0af75c888d83fecefaaad1d018c508881a3bfde2ea640a82790e3567855"
|
content-hash = "5147211bd07992aff3915544175c8d95d77511b9d42273d17c4452fbef9299eb"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ 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"
|
django-polymorphic = "^4.1.0"
|
||||||
|
django-phonenumber-field = {extras = ["phonenumberslite"], version = "^8.4.0"}
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
Reference in New Issue
Block a user