Compare commits
2 Commits
303359c921
...
cdf7e7c677
| Author | SHA1 | Date | |
|---|---|---|---|
| cdf7e7c677 | |||
| b8f4331d3b |
@@ -5,6 +5,7 @@ from openpyxl import Workbook
|
|||||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||||
from openpyxl.utils import get_column_letter
|
from openpyxl.utils import get_column_letter
|
||||||
|
|
||||||
|
from ..models.courses import CourseTypes
|
||||||
from ..models.missions import Training
|
from ..models.missions import Training
|
||||||
from ..models.weekpref import WeekPreference
|
from ..models.weekpref import WeekPreference
|
||||||
from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase
|
from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuildingLegStop, HourBuildingLegBase
|
||||||
@@ -12,6 +13,19 @@ from ..models.hourbuildings import HourBuilding, HourBuildingLegFlight, HourBuil
|
|||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
# Enable cell merging for equal mission
|
||||||
|
MERGE: bool = False
|
||||||
|
|
||||||
|
PALETTE : List[str] = [
|
||||||
|
"#E6F2FF", # azzurro chiarissimo
|
||||||
|
"#E5FBF8", # verde acqua molto chiaro
|
||||||
|
"#ECFBE1", # verde chiarissimo
|
||||||
|
"#FFFBD1", # giallo molto chiaro
|
||||||
|
"#FFF1D6", # giallo-arancio molto chiaro
|
||||||
|
"#FFE3DD", # rosa pesca molto chiaro
|
||||||
|
"#F3E6FA", # lilla chiarissimo
|
||||||
|
]
|
||||||
|
|
||||||
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
|
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
|
||||||
|
|
||||||
if not queryset.first():
|
if not queryset.first():
|
||||||
@@ -84,68 +98,72 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
|
|||||||
student_email: str = q.student.email
|
student_email: str = q.student.email
|
||||||
student_course_type: str
|
student_course_type: str
|
||||||
student_course_number: str
|
student_course_number: str
|
||||||
student_course_ac: str
|
student_course_ac: str = f"({'/'.join(t.type for t in q.student.aircrafts.distinct("type").all())})"
|
||||||
if q.student.course:
|
if q.student.course:
|
||||||
student_course_type = q.student.course.ctype
|
student_course_type = q.student.course.ctype
|
||||||
student_course_number = str(q.student.course.cnumber)
|
student_course_number = str(q.student.course.cnumber)
|
||||||
student_course_ac = " / ".join(t.type for t in q.student.aircrafts.distinct("type").all())
|
|
||||||
student_data = [
|
student_data = [
|
||||||
f"{q.student.surname} {q.student.name}\n{student_course_ac}",
|
"\n".join([f"{q.student.surname} {q.student.name}", student_course_ac]),
|
||||||
f"{student_course_type}-{student_course_number}"
|
f"{student_course_type}-{student_course_number}"
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
|
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
|
||||||
|
|
||||||
# Fill Training mission rows
|
# Fill Training mission rows
|
||||||
mission_name: str
|
mission_name: List[str]
|
||||||
mission_days: List[str]
|
mission_days: List[str]
|
||||||
mission_notes: str
|
mission_notes: str
|
||||||
mission_data: List[List[str]] = []
|
mission_data: List[List[str]] = []
|
||||||
for t in Training.objects.filter(weekpref = q.id):
|
for t in Training.objects.filter(weekpref = q.id):
|
||||||
if not t.mission:
|
if not t.mission:
|
||||||
raise Exception("No Training Mission Assigned")
|
raise Exception("No Training Mission Assigned")
|
||||||
mission_name = f"{t.mission.mtype}-{t.mission.mnum}"
|
mission_notes = t.notes.strip().capitalize() if t.notes else "---"
|
||||||
|
mission_name = [f"{t.mission.mtype}-{t.mission.mnum}"]
|
||||||
|
if q.student.course and q.student.course.ctype == CourseTypes.PPL: # add course aircraft only for PPL students
|
||||||
|
mission_name.append(student_course_ac)
|
||||||
|
mission_name_joined: str = "\n".join(mission_name)
|
||||||
mission_days = [
|
mission_days = [
|
||||||
mission_name if t.monday else "",
|
mission_name_joined if t.monday else "",
|
||||||
mission_name if t.tuesday else "",
|
mission_name_joined if t.tuesday else "",
|
||||||
mission_name if t.wednesday else "",
|
mission_name_joined if t.wednesday else "",
|
||||||
mission_name if t.thursday else "",
|
mission_name_joined if t.thursday else "",
|
||||||
mission_name if t.friday else "",
|
mission_name_joined if t.friday else "",
|
||||||
mission_name if t.saturday else "",
|
mission_name_joined if t.saturday else "",
|
||||||
mission_name if t.sunday else ""
|
mission_name_joined if t.sunday else ""
|
||||||
]
|
]
|
||||||
mission_notes = t.notes.strip() if t.notes else "--"
|
|
||||||
mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ])
|
mission_data.append([str(q.week), *student_data, *mission_days, mission_notes, student_phone, student_email, ])
|
||||||
|
|
||||||
# Fill HourBuilding rows
|
# Fill HourBuilding rows
|
||||||
hb_name: str
|
hb_name: List[str]
|
||||||
hb_days: List[str]
|
hb_days: List[str]
|
||||||
hb_data: List[List[str]] = []
|
hb_data: List[List[str]] = []
|
||||||
for h in HourBuilding.objects.filter(weekpref = q.id):
|
for h in HourBuilding.objects.filter(weekpref = q.id):
|
||||||
hb_name = f"HB - {h.aircraft}\nVedi Note ->"
|
hb_name = ["HB", f"({h.aircraft})"]
|
||||||
hb_days = [
|
|
||||||
hb_name if h.monday else "",
|
|
||||||
hb_name if h.tuesday else "",
|
|
||||||
hb_name if h.wednesday else "",
|
|
||||||
hb_name if h.thursday else "",
|
|
||||||
hb_name if h.friday else "",
|
|
||||||
hb_name if h.saturday else "",
|
|
||||||
hb_name if h.sunday else ""
|
|
||||||
]
|
|
||||||
hb_notes: List[str] = [f"{h.notes.strip()}", "---"] if h.notes else []
|
|
||||||
hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id)
|
hb_legs_all = HourBuildingLegBase.objects.filter(hb_id = h.id)
|
||||||
for hh in hb_legs_all:
|
for hh in hb_legs_all:
|
||||||
time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes
|
time_str: str = ':'.join(str(hh.time).split(':')[:2]) # keep only hours and minutes
|
||||||
if isinstance(hh, HourBuildingLegFlight):
|
if isinstance(hh, HourBuildingLegFlight):
|
||||||
hb_notes.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / PAX: {hh.pax.capitalize()}' if hh.pax else ''}")
|
hb_pax: str | None = " ".join(x.capitalize() for x in hh.pax.split()) if hh.pax else None
|
||||||
|
hb_name.append(f"{hh.departure} -> {hh.destination} [{time_str}]{f' / Pax: {hb_pax}' if hb_pax else ''}")
|
||||||
elif isinstance(hh, HourBuildingLegStop):
|
elif isinstance(hh, HourBuildingLegStop):
|
||||||
hb_notes.append(f"STOP [{time_str}] {"Refuel" if hh.refuel else ""}" )
|
hb_name.append(f"STOP [{time_str}] {"Refuel" if hh.refuel else ""}" )
|
||||||
hb_data.append([str(q.week), *student_data, *hb_days, "\n".join(hb_notes), str(q.student.phone), q.student.email])
|
hb_name_joined: str = "\n".join(hb_name)
|
||||||
|
hb_days = [
|
||||||
|
hb_name_joined if h.monday else "",
|
||||||
|
hb_name_joined if h.tuesday else "",
|
||||||
|
hb_name_joined if h.wednesday else "",
|
||||||
|
hb_name_joined if h.thursday else "",
|
||||||
|
hb_name_joined if h.friday else "",
|
||||||
|
hb_name_joined if h.saturday else "",
|
||||||
|
hb_name_joined if h.sunday else ""
|
||||||
|
]
|
||||||
|
hb_notes: str = h.notes.strip().capitalize() if h.notes else ""
|
||||||
|
hb_data.append([str(q.week), *student_data, *hb_days, hb_notes, str(q.student.phone), q.student.email])
|
||||||
|
|
||||||
# Build rows for table
|
# Build rows for table
|
||||||
all_data: List[List[str]] = mission_data + hb_data
|
all_data: List[List[str]] = mission_data + hb_data
|
||||||
student_start: int = row + row_offset
|
student_start: int = row + row_offset
|
||||||
for row_content in all_data:
|
for ri, row_content in enumerate(all_data):
|
||||||
for c, cell_content in enumerate(row_content, start=1):
|
for c, cell_content in enumerate(row_content, start=1):
|
||||||
cell = ws.cell(row = row + row_offset, column = c, value = cell_content)
|
cell = ws.cell(row = row + row_offset, column = c, value = cell_content)
|
||||||
cell.alignment = center
|
cell.alignment = center
|
||||||
@@ -160,10 +178,10 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
|
|||||||
elif c > course_index and c <= note_index:
|
elif c > course_index and c <= note_index:
|
||||||
cell.border = border_bottom_thin + border_right_thin
|
cell.border = border_bottom_thin + border_right_thin
|
||||||
# Fill mix cells if the cell is not empty
|
# Fill mix cells if the cell is not empty
|
||||||
if c > course_index and c < note_index:
|
if c > course_index and c <= note_index:
|
||||||
if len(cell_content):
|
if len(cell_content):
|
||||||
cell.fill = PatternFill('solid', fgColor="f0f0f0")
|
cell.fill = PatternFill('solid', fgColor=PALETTE[ri % len(PALETTE)].lstrip("#").lower())
|
||||||
|
if MERGE:
|
||||||
prev_cell_val: str = row_content[0]
|
prev_cell_val: str = row_content[0]
|
||||||
merge_start: bool = False
|
merge_start: bool = False
|
||||||
merge_col_start: int = 1
|
merge_col_start: int = 1
|
||||||
@@ -208,6 +226,7 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
|
|||||||
|
|
||||||
# Keep the largest column
|
# Keep the largest column
|
||||||
max_len: List[int] = []
|
max_len: List[int] = []
|
||||||
|
col_letter: str = "A"
|
||||||
for column_cells in ws.columns:
|
for column_cells in ws.columns:
|
||||||
for cell in column_cells:
|
for cell in column_cells:
|
||||||
cell_lines = str(cell.value).splitlines()
|
cell_lines = str(cell.value).splitlines()
|
||||||
@@ -215,11 +234,9 @@ def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) ->
|
|||||||
continue
|
continue
|
||||||
max_len.append(max([len(ll) for ll in cell_lines]))
|
max_len.append(max([len(ll) for ll in cell_lines]))
|
||||||
length: int = max(max_len)
|
length: int = max(max_len)
|
||||||
col_letter: str = "A"
|
|
||||||
if column_cells[0].column:
|
if column_cells[0].column:
|
||||||
col_letter = get_column_letter(column_cells[0].column)
|
col_letter = get_column_letter(column_cells[0].column)
|
||||||
ws.column_dimensions[col_letter].width = length + 2
|
ws.column_dimensions[col_letter].width = length + 2
|
||||||
|
|
||||||
### End of Student Loop ###
|
### End of Student Loop ###
|
||||||
|
|
||||||
# Save document in HttpResponse
|
# Save document in HttpResponse
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ class FlightSlotStaffSite(AdminSite):
|
|||||||
|
|
||||||
# Register only user visible models
|
# Register only user visible models
|
||||||
flightslot_staff = FlightSlotUserSite(name="staff_site")
|
flightslot_staff = FlightSlotUserSite(name="staff_site")
|
||||||
flightslot_staff.register(Availability, AvailabilityAdmin)
|
|
||||||
flightslot_staff.register(MissionProfile, MissionProfileAdmin)
|
flightslot_staff.register(MissionProfile, MissionProfileAdmin)
|
||||||
|
flightslot_staff.register(Availability, AvailabilityAdmin)
|
||||||
flightslot_staff.register(Instructor, InstructorAdmin)
|
flightslot_staff.register(Instructor, InstructorAdmin)
|
||||||
|
|
||||||
# Get version for debug purposes
|
# Get version for debug purposes
|
||||||
@@ -75,5 +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(Instructor, InstructorAdmin)
|
||||||
admin.site.register(Availability, AvailabilityAdmin)
|
#admin.site.register(Availability, AvailabilityAdmin)
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import nested_admin
|
import nested_admin
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db import models
|
from django.db.models import CharField, TextField
|
||||||
|
from django.db.models.query_utils import Q
|
||||||
|
from django.db.models.fields.related import ForeignKey
|
||||||
from django.forms import TextInput, Textarea
|
from django.forms import TextInput, Textarea
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
from ..models.missions import Training
|
from ..models.courses import Course, CourseTypes
|
||||||
|
from ..models.students import Student
|
||||||
|
from ..models.missions import Training, MissionTypes, MissionProfile
|
||||||
from ..models.weekpref import WeekPreference
|
from ..models.weekpref import WeekPreference
|
||||||
|
|
||||||
from ..custom.student_permissions import has_edit_permission
|
from ..custom.student_permissions import has_edit_permission
|
||||||
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
class TrainingForm(forms.ModelForm):
|
class TrainingForm(forms.ModelForm):
|
||||||
model=Training
|
model=Training
|
||||||
|
|
||||||
@@ -23,10 +25,30 @@ class TrainingInLIne(nested_admin.NestedTabularInline):
|
|||||||
max_num = 7
|
max_num = 7
|
||||||
|
|
||||||
formfield_overrides = {
|
formfield_overrides = {
|
||||||
models.CharField: {'widget': TextInput(attrs={'size':'20'})},
|
CharField: {'widget': TextInput(attrs={'size':'20'})},
|
||||||
models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':35})},
|
TextField: {'widget': Textarea(attrs={'rows':4, 'cols':35})},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def formfield_for_foreignkey(self, db_field: ForeignKey, request: HttpRequest, **kwargs):
|
||||||
|
# modify entries for "mission" field, show only types compatible with student course
|
||||||
|
if not hasattr(request.user, "student") or not hasattr(request.user.student, "course"):
|
||||||
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
course: Course = request.user.student.course
|
||||||
|
if db_field.name == "mission":
|
||||||
|
match course.ctype:
|
||||||
|
case CourseTypes.PPL:
|
||||||
|
kwargs["queryset"] = MissionProfile.objects.filter(mtype=MissionTypes.PPL)
|
||||||
|
case CourseTypes.ATPL:
|
||||||
|
q: Q = Q(mtype=MissionTypes.IR) | \
|
||||||
|
Q(mtype=MissionTypes.MEP) | \
|
||||||
|
Q(mtype=MissionTypes.MEP_IR) | \
|
||||||
|
Q(mtype=MissionTypes.CPL) | \
|
||||||
|
Q(mtype=MissionTypes.CHK)
|
||||||
|
kwargs["queryset"] = MissionProfile.objects.filter(q).order_by("id")
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
# 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:
|
||||||
return has_edit_permission(request=request, obj=obj)
|
return has_edit_permission(request=request, obj=obj)
|
||||||
|
|||||||
@@ -15,7 +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 ..custom.student_permissions import has_edit_permission, has_week_add_permission
|
||||||
from ..actions.exportweek import export_selected
|
from ..actions.exportweek import export_selected
|
||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
@@ -93,7 +93,7 @@ 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_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
def has_add_permission(self, request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
return not obj and self.has_change_permission(request, obj)
|
return has_week_add_permission(request=request) and has_edit_permission(request=request, obj=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:
|
||||||
@@ -110,7 +110,7 @@ class WeekPreferenceAdmin(nested_admin.NestedPolymorphicModelAdmin):
|
|||||||
extra_context["show_delete"] = False
|
extra_context["show_delete"] = False
|
||||||
return super().changeform_view(request, object_id, form_url, extra_context)
|
return super().changeform_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
def save_model(self, request: HttpRequest, obj, form: Form, change: bool):
|
def save_model(self, request: HttpRequest, obj: WeekPreference, form: Form, change: bool):
|
||||||
# Imposta automaticamente lo studente se non è già valorizzato
|
# Imposta automaticamente lo studente se non è già valorizzato
|
||||||
if hasattr(request.user, "student") and not obj.student_id:
|
if hasattr(request.user, "student") and not obj.student_id:
|
||||||
obj.student = request.user.student
|
obj.student = request.user.student
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ from ..models.weekpref import WeekPreference
|
|||||||
|
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
def has_week_add_permission(request: HttpRequest):
|
||||||
|
if hasattr(request.user, 'student'):
|
||||||
|
student: Student = request.user.student
|
||||||
|
current_week: int = date.today().isocalendar().week
|
||||||
|
return student.active and not WeekPreference.objects.filter(student_id=student.id, week=current_week).count()
|
||||||
|
return True
|
||||||
|
|
||||||
# allow add, modify, delete depending on a set of requirements
|
# allow add, modify, delete depending on a set of requirements
|
||||||
def has_edit_permission(request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
def has_edit_permission(request: HttpRequest, obj: WeekPreference | None = None) -> bool:
|
||||||
if hasattr(request.user, 'student'):
|
if hasattr(request.user, 'student'):
|
||||||
student: Student = request.user.student
|
|
||||||
if not student.active:
|
|
||||||
return False
|
|
||||||
current_week: int = date.today().isocalendar().week
|
current_week: int = date.today().isocalendar().week
|
||||||
if obj and (current_week > obj.week or not student.active):
|
if obj and current_week > obj.week:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|||||||
6
note.txt
Normal file
6
note.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
OK leg delle hb all'interno del riquadro per i giorni
|
||||||
|
OK aereo assegnato allo studente di fianco al numero della missione per PPL, invece per CPL e IR e HB il tipo va di fiaco al nome dello studente
|
||||||
|
OK le missioni ripetute su piu' giorni hanno una cella per giorno (non unite)
|
||||||
|
lo studente vede solo le missioni della sua fase PPL->PPL ATPL-> tutto
|
||||||
|
OK ogni richiesta ha un colore diverso che cicla con delle tinte pastello
|
||||||
|
|
||||||
Reference in New Issue
Block a user