Renamed Project to cntmanage
This commit is contained in:
0
cntmanage/flightslot/__init__.py
Normal file
0
cntmanage/flightslot/__init__.py
Normal file
198
cntmanage/flightslot/actions/exportweek.py
Normal file
198
cntmanage/flightslot/actions/exportweek.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
from datetime import date, datetime
|
||||
from typing import List
|
||||
|
||||
from ..models.weekpref import WeekPreference
|
||||
from ..models.missions import Training
|
||||
from ..models.hourbuildings import HourBuilding,HourBuildingLeg
|
||||
|
||||
|
||||
def export_selected(request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse:
|
||||
|
||||
if not queryset.first():
|
||||
raise Exception("Empty queryset")
|
||||
|
||||
# Init Variables
|
||||
year = date.today().year
|
||||
week = queryset.first().week if queryset.first() else date.today().isocalendar().week
|
||||
weeks = queryset.order_by("week").distinct("week").all()
|
||||
|
||||
# Prepare export filename and http content
|
||||
filename = f"{year}_week{'+'.join([str(w.week) for w in weeks])}_export.xlsx"
|
||||
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
|
||||
# Create workbook and sheet
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
if not ws:
|
||||
raise Exception("Export: cannot select active workbook")
|
||||
ws.title = f"Week Preferences"
|
||||
ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
|
||||
ws.page_setup.paperSize = ws.PAPERSIZE_A3
|
||||
ws.page_setup.fitToHeight = 0
|
||||
ws.page_setup.fitToWidth = 1
|
||||
|
||||
# Header titles
|
||||
days = [f"{datetime.strptime(f"{year} {week} {x}", "%G %V %u").strftime("%A")} {datetime.strptime(f"{year} {week} {x}", "%G %V %u").day}" for x in range(1,8)]
|
||||
headers = ["Week", "Student", "Course", *days, "Cell.", "Mail", "Notes"]
|
||||
|
||||
# Header fields positions
|
||||
week_index: int = headers.index("Week") + 1
|
||||
student_index: int = headers.index("Student") + 1
|
||||
course_index: int = headers.index("Course") + 1
|
||||
cell_index: int = headers.index("Cell.") + 1
|
||||
mail_index: int = headers.index("Mail") + 1
|
||||
note_index: int = headers.index("Notes") + 1
|
||||
|
||||
# Stile header
|
||||
header_fill = PatternFill("solid", fgColor="0e005c")
|
||||
bold_white = Font(color="FFFFFF", bold=True)
|
||||
bold_black = Font(color="000000", bold=True)
|
||||
center = Alignment(horizontal="center", vertical="center", wrapText=True)
|
||||
|
||||
# Cell styles
|
||||
border_thick: Side = Side(style='thick', color='000000')
|
||||
border_bottom: Border = Border(bottom=border_thick)
|
||||
border_all: Border = Border(bottom=border_thick, top=border_thick, left=border_thick, right=None)
|
||||
|
||||
# Scrittura header
|
||||
for col, h in enumerate(headers, start=1):
|
||||
cell = ws.cell(row=1, column=col, value=h)
|
||||
cell.fill = header_fill
|
||||
cell.font = bold_white
|
||||
cell.alignment = center
|
||||
|
||||
### Start of Student Loop ###
|
||||
# Fill worksheet with EVERY training and hb for every student
|
||||
# Each of this iterations fills the table for a student
|
||||
row: int = 2
|
||||
row_offset: int = 0
|
||||
for i, q in enumerate(queryset.order_by("student__surname", "student__name", "student__course"), start=1):
|
||||
student_data: List[str]
|
||||
student_phone: str = q.student.phone if q.student.phone else ""
|
||||
student_email: str = q.student.email
|
||||
if q.student.course:
|
||||
student_data = [f"{q.student.surname} {q.student.name}", f"{q.student.course.ctype}-{q.student.course.cnumber}"]
|
||||
else:
|
||||
student_data = [f"{q.student.surname} {q.student.name}", f"No Course Assigned"]
|
||||
|
||||
# Fill Training mission rows
|
||||
mission_name: str
|
||||
mission_days: List[str]
|
||||
mission_notes: str
|
||||
mission_data: List[List[str]] = []
|
||||
for t in Training.objects.filter(weekpref = q.id):
|
||||
if not t.mission:
|
||||
raise Exception("No Training Mission Assigned")
|
||||
mission_name = f"{t.mission.mtype}-{t.mission.mnum}"
|
||||
mission_days = [
|
||||
mission_name if t.monday else "",
|
||||
mission_name if t.tuesday else "",
|
||||
mission_name if t.wednesday else "",
|
||||
mission_name if t.thursday else "",
|
||||
mission_name if t.friday else "",
|
||||
mission_name if t.saturday else "",
|
||||
mission_name if t.sunday else ""
|
||||
]
|
||||
mission_notes = t.notes if t.notes else "--"
|
||||
mission_data.append([str(q.week), *student_data, *mission_days, student_phone, student_email, mission_notes])
|
||||
|
||||
# Fill HourBuilding rows
|
||||
hb_name: str
|
||||
hb_days: List[str]
|
||||
hb_notes: str
|
||||
hb_data: List[List[str]] = []
|
||||
for h in HourBuilding.objects.filter(weekpref = q.id):
|
||||
hb_name = f"HB-{h.aircraft}\nVedi Note ->"
|
||||
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 = f"{h.notes}\n----\n" if h.notes else ""
|
||||
hb_legs = HourBuildingLeg.objects.filter(hb_id = h.id)
|
||||
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.strip('\n')
|
||||
hb_data.append([str(q.week), *student_data, *hb_days, str(q.student.phone), q.student.email, hb_notes])
|
||||
|
||||
# Build rows for table
|
||||
all_data: List[List[str]] = mission_data + hb_data
|
||||
student_start: int = row + row_offset
|
||||
for r in all_data:
|
||||
for j, c in enumerate(r, start=1):
|
||||
cell = ws.cell(row = row + row_offset, column = j, value = c)
|
||||
cell.alignment = center
|
||||
# Format Student Name
|
||||
if j == student_index:
|
||||
cell.font = bold_black
|
||||
# Format Course Column
|
||||
if j == course_index and q.student.course:
|
||||
cell.font = bold_black
|
||||
cell.fill = PatternFill("solid", fgColor=str(q.student.course.color).lstrip('#').lower())
|
||||
|
||||
prev_cell_val: str = r[0]
|
||||
merge_start: bool = False
|
||||
merge_col_start: int = 1
|
||||
for c, v in enumerate(r, start=1):
|
||||
# Merge cells in the row
|
||||
if v == prev_cell_val and not merge_start:
|
||||
merge_start = True
|
||||
merge_col_start = c-1 # start merge from previous column
|
||||
elif v != prev_cell_val and merge_start:
|
||||
merge_start = False
|
||||
ws.merge_cells(start_row=row+row_offset,
|
||||
end_row=row+row_offset,
|
||||
start_column=max(merge_col_start,1),
|
||||
end_column=max(c-1,1)) # end merge to previous column
|
||||
prev_cell_val = v
|
||||
|
||||
# Incement row counter
|
||||
row_offset += 1
|
||||
|
||||
# End week preferences for this student
|
||||
student_end: int = row + row_offset -1
|
||||
|
||||
# Merge Week, thick border
|
||||
# ws.cell(row=student_start, column=week_index).border = border_all
|
||||
# ws.merge_cells(start_row=student_start, end_row=student_end, start_column=week_index, end_column=week_index)
|
||||
# Merge Name, thick border
|
||||
ws.cell(row=student_start, column=student_index).border = border_all
|
||||
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=student_index, end_column=student_index)
|
||||
# Merge Course
|
||||
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=course_index, end_column=course_index)
|
||||
# Merge Cell
|
||||
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=cell_index, end_column=cell_index)
|
||||
# Merge Mail
|
||||
ws.merge_cells(start_row=student_start, end_row=student_end, start_column=mail_index, end_column=mail_index)
|
||||
|
||||
# Add thick border to the last cell row of this student
|
||||
for i in range(course_index, len(all_data[0])+1):
|
||||
ws.cell(row=student_end, column=i).border = border_bottom
|
||||
|
||||
# Keep the largest column
|
||||
for column_cells in ws.columns:
|
||||
length: int = max(len(str(cell.value)) for cell in column_cells)
|
||||
col_letter: str = "A"
|
||||
if column_cells[0].column:
|
||||
col_letter = get_column_letter(column_cells[0].column)
|
||||
ws.column_dimensions[col_letter].width = length + 2
|
||||
|
||||
### End of Student Loop ###
|
||||
|
||||
|
||||
# Save document in HttpResponse
|
||||
wb.save(response)
|
||||
|
||||
return response
|
||||
16
cntmanage/flightslot/admin.py
Normal file
16
cntmanage/flightslot/admin.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models.courses import Course
|
||||
from .models.students import Student
|
||||
from .models.missions import MissionProfile
|
||||
from .models.weekpref import WeekPreference
|
||||
|
||||
from .admins.course_adm import CourseAdmin
|
||||
from .admins.student_adm import StudentAdmin
|
||||
from .admins.mission_adm import MissionProfileAdmin
|
||||
from .admins.weekpred_adm import WeekPreferenceAdmin
|
||||
|
||||
admin.site.register(Course, CourseAdmin)
|
||||
admin.site.register(MissionProfile, MissionProfileAdmin)
|
||||
admin.site.register(Student, StudentAdmin)
|
||||
admin.site.register(WeekPreference, WeekPreferenceAdmin)
|
||||
16
cntmanage/flightslot/admins/course_adm.py
Normal file
16
cntmanage/flightslot/admins/course_adm.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
from ..models.courses import Course
|
||||
from ..custom.colortag import course_color
|
||||
|
||||
class CourseAdmin(admin.ModelAdmin):
|
||||
list_display = ("ctype", "cnumber","color_display", "year")
|
||||
list_filter = ("ctype", "year")
|
||||
|
||||
# Dinamically add color_display property to show a colored dot
|
||||
@admin.display(description="Color")
|
||||
def color_display(self, obj: Course) -> SafeText:
|
||||
if not obj.pk:
|
||||
return SafeText("")
|
||||
return course_color(obj.color)
|
||||
70
cntmanage/flightslot/admins/hourbuilding_adm.py
Normal file
70
cntmanage/flightslot/admins/hourbuilding_adm.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import nested_admin
|
||||
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.forms import TextInput, Textarea
|
||||
from django.http import HttpRequest
|
||||
|
||||
from durationwidget.widgets import TimeDurationWidget
|
||||
|
||||
from ..models.hourbuildings import HourBuilding, HourBuildingLeg
|
||||
from ..models.weekpref import WeekPreference
|
||||
|
||||
from datetime import date
|
||||
|
||||
class HourBuildingLegForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = HourBuildingLeg
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'time': TimeDurationWidget(show_days=False,
|
||||
show_seconds=False
|
||||
)
|
||||
}
|
||||
|
||||
# Register your models here.
|
||||
class HourBuildingLegInline(nested_admin.NestedTabularInline):
|
||||
model = HourBuildingLeg
|
||||
form = HourBuildingLegForm
|
||||
extra = 0
|
||||
fk_name = 'hb'
|
||||
max_num = 5
|
||||
formfield_overrides = {
|
||||
models.CharField: {'widget': TextInput(attrs={'size':'20'})},
|
||||
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: HourBuilding | None = None):
|
||||
if hasattr(request.user, 'student') and obj:
|
||||
current_week = date.today().isocalendar().week
|
||||
if not obj.DoesNotExist and current_week > obj.weekpref.week:
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_delete_permission(self, request: HttpRequest, obj: HourBuilding | None = None):
|
||||
return self.has_change_permission(request=request, obj=obj)
|
||||
|
||||
class HourBuildingInLine(nested_admin.NestedTabularInline):
|
||||
model = HourBuilding
|
||||
extra = 0
|
||||
inlines = [HourBuildingLegInline]
|
||||
fk_name = 'weekpref'
|
||||
verbose_name_plural = "Hour Building"
|
||||
max_num = 7
|
||||
formfield_overrides = {
|
||||
models.CharField: {'widget': TextInput(attrs={'size':'20'})},
|
||||
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)
|
||||
39
cntmanage/flightslot/admins/mission_adm.py
Normal file
39
cntmanage/flightslot/admins/mission_adm.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
from typing import Any
|
||||
|
||||
from import_export import fields
|
||||
from import_export.admin import ImportMixin
|
||||
from import_export.resources import ModelResource
|
||||
|
||||
from ..models.missions import MissionProfile
|
||||
|
||||
from django_admin_action_forms import AdminActionFormsMixin
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
# Resource Class for Student data import
|
||||
class MissionProfileResource(ModelResource):
|
||||
mtype = fields.Field(attribute="mtype", column_name="mtype")
|
||||
mnum = fields.Field(attribute="mnum", column_name="mnum")
|
||||
duration = fields.Field(attribute="duration", column_name="duration")
|
||||
|
||||
# Cleanup fields before entering
|
||||
def before_import_row(self, row: dict[str, str | Any], **kwargs) -> None:
|
||||
row["mtype"] = SafeText(row["mtype"].upper().strip())
|
||||
row["mnum"] = SafeText(row["mnum"].upper().strip())
|
||||
h, m, _ = row["duration"].split(":")
|
||||
row["duration"] = timedelta(hours=float(h), minutes=float(m))
|
||||
return super().before_import_row(row, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = MissionProfile
|
||||
skip_unchanged = True
|
||||
report_skipped = True
|
||||
fields = ("mtype", "mnum", "duration")
|
||||
import_id_fields = ("mtype", "mnum")
|
||||
|
||||
class MissionProfileAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||
list_display = ("mtype", "mnum", "notes")
|
||||
list_filter = ("mtype",)
|
||||
75
cntmanage/flightslot/admins/student_adm.py
Normal file
75
cntmanage/flightslot/admins/student_adm.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from django import forms
|
||||
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 import_export import fields
|
||||
from import_export.admin import ImportMixin
|
||||
from import_export.resources import ModelResource
|
||||
from import_export.widgets import CharWidget
|
||||
|
||||
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||
|
||||
from ..models.courses import Course
|
||||
from ..models.students import Student
|
||||
|
||||
from ..custom.colortag import course_color
|
||||
|
||||
# Resource Class for Student data import
|
||||
class StudentResource(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(row["name"].capitalize().strip())
|
||||
row["surname"] = SafeText(row["surname"].capitalize().strip())
|
||||
row["phone"] = SafeText(row["phone"].replace(" ",""))
|
||||
row["email"] = SafeText(row["email"].lower().strip())
|
||||
return super().before_import_row(row, **kwargs)
|
||||
|
||||
class Meta:
|
||||
model = Student
|
||||
skip_unchanged = True
|
||||
report_skipped = True
|
||||
fields = ("surname", "name", "email", "phone")
|
||||
import_id_fields = ("email", "phone")
|
||||
|
||||
# Form Class for Student course change
|
||||
class ChangeCourseForm(AdminActionForm):
|
||||
course = forms.ModelChoiceField(queryset=Course.objects.all())
|
||||
|
||||
class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||
list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active")
|
||||
list_filter = ("course", "active")
|
||||
actions = ("change_course", "disable_students")
|
||||
resource_classes = [StudentResource]
|
||||
|
||||
@admin.display(description="Color")
|
||||
def course_color(self, obj: Student) -> SafeText:
|
||||
if not obj.course:
|
||||
return SafeText("")
|
||||
return course_color(obj.course.color)
|
||||
|
||||
@admin.display(description="Password")
|
||||
def password(self, obj: Student) -> SafeText:
|
||||
return SafeText(obj.default_password())
|
||||
|
||||
@admin.action(description="Disable Students")
|
||||
def disable_students(self, request: HttpRequest, queryset: QuerySet[Student]):
|
||||
for q in queryset.all():
|
||||
if q.user:
|
||||
q.user.is_staff = False
|
||||
q.user.save()
|
||||
count: int = queryset.update(active = False)
|
||||
messages.success(request, f"{count} students deactivated")
|
||||
pass
|
||||
|
||||
@action_with_form(ChangeCourseForm, description="Change Student Course")
|
||||
def change_course(self, request: HttpRequest, queryset: QuerySet[Student], data):
|
||||
course = data["course"]
|
||||
count: int = queryset.update(course=course)
|
||||
messages.success(request, f"{count} students updated to {course}")
|
||||
37
cntmanage/flightslot/admins/training_adm.py
Normal file
37
cntmanage/flightslot/admins/training_adm.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import nested_admin
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.forms import TextInput, Textarea
|
||||
from django.http import HttpRequest
|
||||
|
||||
from ..models.missions import Training
|
||||
from ..models.weekpref import WeekPreference
|
||||
|
||||
from datetime import date
|
||||
|
||||
class TrainingForm(forms.ModelForm):
|
||||
model=Training
|
||||
|
||||
class TrainingInLIne(nested_admin.NestedTabularInline):
|
||||
model = Training
|
||||
form = TrainingForm
|
||||
extra = 0
|
||||
fk_name = 'weekpref'
|
||||
verbose_name_plural = "Training Missions"
|
||||
max_num = 7
|
||||
|
||||
formfield_overrides = {
|
||||
models.CharField: {'widget': TextInput(attrs={'size':'20'})},
|
||||
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: 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):
|
||||
return self.has_change_permission(request=request, obj=obj)
|
||||
126
cntmanage/flightslot/admins/weekpred_adm.py
Normal file
126
cntmanage/flightslot/admins/weekpred_adm.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import nested_admin
|
||||
|
||||
from django import forms
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.contrib import admin, messages
|
||||
from django.utils.translation import ngettext
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
from ..models.missions import Training
|
||||
from ..models.weekpref import WeekPreference
|
||||
|
||||
from .training_adm import TrainingInLIne
|
||||
from .hourbuilding_adm import HourBuildingInLine
|
||||
|
||||
from ..custom.colortag import course_color
|
||||
from ..actions.exportweek import export_selected
|
||||
|
||||
from datetime import date
|
||||
|
||||
class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
||||
inlines = (TrainingInLIne, HourBuildingInLine,)
|
||||
list_display = ("week", "student__surname","student__name", "student__course", "course_color", "student_brief_mix",)
|
||||
list_filter = ("week", "student__course", "student",)
|
||||
actions = ("export",)
|
||||
|
||||
@admin.action(description="Export Selected Preferences")
|
||||
def export(self, request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse | None:
|
||||
if queryset.count() == 0:
|
||||
return None
|
||||
self.message_user(request, ngettext("Exporting %d row", "Exporting %d rows", queryset.count()) % queryset.count(), messages.SUCCESS)
|
||||
return export_selected(request=request, queryset=queryset)
|
||||
|
||||
@admin.display(description="Mission Count")
|
||||
def student_brief_mix(self, obj: WeekPreference) -> SafeText:
|
||||
if not obj.student.course:
|
||||
return SafeText("")
|
||||
return SafeText(f"{Training.objects.filter(weekpref = obj.id).count()}")
|
||||
|
||||
@admin.display(description="Color")
|
||||
def course_color(self, obj: WeekPreference) -> SafeText:
|
||||
if not obj.student.course:
|
||||
return SafeText("")
|
||||
return course_color(obj.student.course.color)
|
||||
|
||||
# If a user is registered as student hide filters
|
||||
def get_list_filter(self, request):
|
||||
list_filter = super().get_list_filter(request)
|
||||
if hasattr(request.user, 'student'):
|
||||
return []
|
||||
return list_filter
|
||||
|
||||
# If a user is registered as student do not show actions
|
||||
def get_actions(self, request):
|
||||
actions = super().get_actions(request)
|
||||
if hasattr(request.user, 'student'):
|
||||
return []
|
||||
return actions
|
||||
|
||||
# If a user is registered as student show only their preferences
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
if hasattr(request.user, 'student'):
|
||||
return qs.filter(student=request.user.student)
|
||||
# If admin show everything
|
||||
return qs
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form: forms.Form = super().get_form(request, obj, **kwargs)
|
||||
current_week = date.today().isocalendar().week
|
||||
|
||||
# If form contains the week field
|
||||
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, 'student'):
|
||||
student = request.user.student
|
||||
if 'student' in form.base_fields:
|
||||
form.base_fields['student'].initial = student
|
||||
form.base_fields['student'].disabled = True
|
||||
form.base_fields['week'].disabled = True # student cannot change week
|
||||
return form
|
||||
|
||||
# If user is a student deny edit permission for week past the current one
|
||||
def has_change_permission(self, request, 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
|
||||
|
||||
# If user is a student deny edit permission for week past the current one
|
||||
def has_add_permission(self, request, 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
|
||||
|
||||
# If user is a student deny edit permission for week past the current one
|
||||
def has_delete_permission(self, request, 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 changeform_view(self, request: HttpRequest, object_id: int | None = None, form_url: str = '', extra_context=None):
|
||||
extra_context = extra_context or {}
|
||||
if hasattr(request.user, 'student') and object_id:
|
||||
current_week = date.today().isocalendar().week
|
||||
weekpref = WeekPreference.objects.get(id=object_id)
|
||||
if current_week > weekpref.week:
|
||||
extra_context['show_save'] = False
|
||||
extra_context['show_save_and_continue'] = False
|
||||
extra_context['show_save_and_add_another'] = False
|
||||
extra_context['show_delete'] = False
|
||||
return super().changeform_view(request, object_id, form_url, extra_context)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
# Imposta automaticamente lo studente se non è già valorizzato
|
||||
if hasattr(request.user, 'student') and not obj.student_id:
|
||||
obj.student = request.user.student
|
||||
super().save_model(request, obj, form, change)
|
||||
5
cntmanage/flightslot/apps.py
Normal file
5
cntmanage/flightslot/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class FlightslotConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'flightslot'
|
||||
18
cntmanage/flightslot/custom/colortag.py
Normal file
18
cntmanage/flightslot/custom/colortag.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from colorfield.fields import ColorField
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import SafeText
|
||||
|
||||
def course_color(color: ColorField | None) -> SafeText:
|
||||
if not color:
|
||||
return format_html("")
|
||||
return format_html(
|
||||
'<div style="background-color: {}; \
|
||||
width: 20px; height: 20px; \
|
||||
border-radius: 25%; \
|
||||
border-color: gray; \
|
||||
border-style: solid; \
|
||||
border-width: 1px; \
|
||||
display: inline-block; \
|
||||
"></div>',
|
||||
color
|
||||
)
|
||||
23
cntmanage/flightslot/migrations/0001_initial.py
Normal file
23
cntmanage/flightslot/migrations/0001_initial.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-19 16:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Student',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('email', models.EmailField(db_index=True, max_length=254)),
|
||||
('name', models.CharField(max_length=32)),
|
||||
('surname', models.CharField(max_length=32)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-19 17:49
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
import django.db.models.functions.datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MissionProfile',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('mtype', models.IntegerField(choices=[('OTHER', 'OTHER'), ('HB', 'HB'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='HB')),
|
||||
('mnum', models.PositiveSmallIntegerField(default=0, null=True)),
|
||||
('duration', models.DurationField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WeekPreference',
|
||||
fields=[
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('week', models.PositiveSmallIntegerField(db_default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1)), db_index=True)),
|
||||
('student', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.student')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Preference',
|
||||
fields=[
|
||||
('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)),
|
||||
('saturday', models.BooleanField(default=True)),
|
||||
('sunday', models.BooleanField(default=True)),
|
||||
('mission', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.missionprofile')),
|
||||
('weekpref', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.weekpreference')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-19 17:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0002_missionprofile_weekpreference_preference'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='mtype',
|
||||
field=models.CharField(choices=[('OTHER', 'OTHER'), ('HB', 'HB'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='HB'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,65 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-20 07:57
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
import django.db.models.functions.datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0003_alter_missionprofile_mtype'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='Preference',
|
||||
new_name='Training',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='missionprofile',
|
||||
name='notes',
|
||||
field=models.TextField(max_length=140, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='duration',
|
||||
field=models.DurationField(default=1),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='mtype',
|
||||
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='weekpreference',
|
||||
name='week',
|
||||
field=models.PositiveSmallIntegerField(auto_created=True, db_index=True, default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1))),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HourBuilding',
|
||||
fields=[
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('aircraft', models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('C182', 'Cessna 182Q'), ('P210', 'Tecnam P2010')])),
|
||||
('monday', models.BooleanField(default=True)),
|
||||
('tuesday', models.BooleanField(default=True)),
|
||||
('wednesday', models.BooleanField(default=True)),
|
||||
('thursday', models.BooleanField(default=True)),
|
||||
('saturday', models.BooleanField(default=True)),
|
||||
('sunday', models.BooleanField(default=True)),
|
||||
('weekpref', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.weekpreference')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HourBuildingLeg',
|
||||
fields=[
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('departure', models.CharField(default='LILV', max_length=4)),
|
||||
('destination', models.CharField(default='LILV', max_length=4)),
|
||||
('time', models.DurationField(default=1)),
|
||||
('stop', models.BooleanField(default=False)),
|
||||
('hb', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.hourbuilding')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-20 08:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0004_rename_preference_training_missionprofile_notes_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='hourbuilding',
|
||||
name='notes',
|
||||
field=models.TextField(max_length=140, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='training',
|
||||
name='notes',
|
||||
field=models.TextField(max_length=140, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hourbuilding',
|
||||
name='weekpref',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.weekpreference'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='training',
|
||||
name='mission',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='flightslot.missionprofile'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='training',
|
||||
name='weekpref',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='flightslot.weekpreference'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-20 09:14
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.db.models.functions.datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0005_hourbuilding_notes_training_notes_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Course',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('ctype', models.CharField(choices=[('PPL', 'PPL'), ('ATPL', 'ATPL')])),
|
||||
('cnumber', models.PositiveSmallIntegerField(default=django.db.models.functions.datetime.ExtractYear(django.db.models.functions.datetime.Now()))),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='active',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='course',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.course'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-20 09:27
|
||||
|
||||
import django.db.models.functions.datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0006_course_student_active_student_course'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='course',
|
||||
name='year',
|
||||
field=models.PositiveSmallIntegerField(db_default=django.db.models.functions.datetime.ExtractYear(django.db.models.functions.datetime.Now()), editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hourbuilding',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True, max_length=140, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True, max_length=140, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='training',
|
||||
name='notes',
|
||||
field=models.TextField(blank=True, max_length=140, null=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.2 on 2024-10-20 10:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0007_course_year_alter_hourbuilding_notes_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='hourbuilding',
|
||||
name='friday',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='training',
|
||||
name='friday',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-11 17:45
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
import django.db.models.functions.datetime
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0008_hourbuilding_friday_training_friday'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='user',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='mnum',
|
||||
field=models.PositiveSmallIntegerField(default=0, null=True, verbose_name='Mission Number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='mtype',
|
||||
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL', verbose_name='Mission Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='weekpreference',
|
||||
name='student',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='flightslot.student', verbose_name='Student Selection'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='weekpreference',
|
||||
name='week',
|
||||
field=models.PositiveSmallIntegerField(auto_created=True, db_index=True, default=django.db.models.expressions.CombinedExpression(django.db.models.functions.datetime.ExtractWeek(django.db.models.functions.datetime.Now()), '+', models.Value(1)), verbose_name='Week Number'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-11 20:31
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0009_student_user_alter_missionprofile_mnum_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='hourbuildingleg',
|
||||
name='time',
|
||||
field=models.DurationField(default=models.DurationField(default=datetime.timedelta(0))),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-11 20:33
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0010_alter_hourbuildingleg_time'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='hourbuildingleg',
|
||||
name='time',
|
||||
field=models.DurationField(default=datetime.timedelta(seconds=3600)),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-12 09:47
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0011_alter_hourbuildingleg_time'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='cnumber',
|
||||
field=models.PositiveSmallIntegerField(default=2025),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='year',
|
||||
field=models.PositiveSmallIntegerField(db_default=2025, editable=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='duration',
|
||||
field=models.DurationField(default=datetime.timedelta(seconds=3600)),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='weekpreference',
|
||||
name='week',
|
||||
field=models.PositiveSmallIntegerField(auto_created=True, db_default=46, db_index=True, verbose_name='Week Number'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-12 10:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0012_alter_course_cnumber_alter_course_year_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='course',
|
||||
name='color',
|
||||
field=models.CharField(choices=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')], default='#ffffff'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='ctype',
|
||||
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('OTHER', 'OTHER')]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='year',
|
||||
field=models.PositiveSmallIntegerField(default=2025),
|
||||
),
|
||||
]
|
||||
18
cntmanage/flightslot/migrations/0014_student_phone.py
Normal file
18
cntmanage/flightslot/migrations/0014_student_phone.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-12 10:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0013_course_color_alter_course_ctype_alter_course_year'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='phone',
|
||||
field=models.CharField(max_length=16, null=True),
|
||||
),
|
||||
]
|
||||
19
cntmanage/flightslot/migrations/0015_alter_course_color.py
Normal file
19
cntmanage/flightslot/migrations/0015_alter_course_color.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-12 10:31
|
||||
|
||||
import colorfield.fields
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0014_student_phone'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='color',
|
||||
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')]),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-14 14:42
|
||||
|
||||
import colorfield.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0015_alter_course_color'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='cnumber',
|
||||
field=models.PositiveSmallIntegerField(default=2025, verbose_name='Course Number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='color',
|
||||
field=colorfield.fields.ColorField(default='#FFFFFF', image_field=None, max_length=25, samples=[('#ffffff', 'WHITE'), ('#ff0000', 'RED'), ('#00ff00', 'GREEN'), ('#0000ff', 'BLUE')], verbose_name='Binder Color'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='ctype',
|
||||
field=models.CharField(choices=[('FI', 'FI'), ('PPL', 'PPL'), ('ATPL', 'ATPL'), ('DL', 'DISTANCE'), ('OTHER', 'OTHER')], verbose_name='Course Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='course',
|
||||
name='year',
|
||||
field=models.PositiveSmallIntegerField(default=2025, verbose_name='Year'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hourbuilding',
|
||||
name='aircraft',
|
||||
field=models.CharField(choices=[('C152', 'Cessna 152'), ('P208', 'Tecnam P2008'), ('PA28', 'Piper PA28R'), ('C182', 'Cessna 182Q'), ('TWEN', 'Tecnam P2010')]),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-18 21:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('flightslot', '0016_alter_course_cnumber_alter_course_color_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='missionprofile',
|
||||
name='mtype',
|
||||
field=models.CharField(choices=[('OTHER', 'OTHER'), ('PPL', 'PPL'), ('IR', 'IR'), ('MEP', 'MEP'), ('CPL', 'CPL'), ('FI', 'FI'), ('PC', 'PC'), ('CHK', 'CHK_6M')], default='PPL', verbose_name='Mission Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='weekpreference',
|
||||
name='week',
|
||||
field=models.PositiveSmallIntegerField(auto_created=True, db_default=47, db_index=True, verbose_name='Week Number'),
|
||||
),
|
||||
]
|
||||
0
cntmanage/flightslot/migrations/__init__.py
Normal file
0
cntmanage/flightslot/migrations/__init__.py
Normal file
9
cntmanage/flightslot/models/aircrafts.py
Normal file
9
cntmanage/flightslot/models/aircrafts.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
|
||||
class AircraftTypes(models.TextChoices):
|
||||
C152 = "C152", _("Cessna 152")
|
||||
P208 = "P208", _("Tecnam P2008")
|
||||
PA28 = "PA28", _("Piper PA28R")
|
||||
C182 = "C182", _("Cessna 182Q")
|
||||
P210 = "TWEN", _("Tecnam P2010")
|
||||
52
cntmanage/flightslot/models/courses.py
Normal file
52
cntmanage/flightslot/models/courses.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
from datetime import date
|
||||
from colorfield.fields import ColorField
|
||||
|
||||
class CourseTypes(models.TextChoices):
|
||||
FI = "FI", _("FI")
|
||||
PPL = "PPL", _("PPL")
|
||||
ATPL = "ATPL", _("ATPL")
|
||||
DISTANCE = "DL", _("DISTANCE")
|
||||
OTHER = "OTHER",_("OTHER")
|
||||
|
||||
|
||||
class Course(models.Model):
|
||||
# Add colors according to table from Alessia
|
||||
COLOR_PALETTE = [
|
||||
("#ffffff","WHITE"),
|
||||
("#ff0000", "RED"),
|
||||
("#00ff00", "GREEN"),
|
||||
("#0000ff", "BLUE")
|
||||
]
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
ctype = models.CharField(
|
||||
null=False,
|
||||
choices=CourseTypes,
|
||||
verbose_name=_("Course Type")
|
||||
)
|
||||
|
||||
cnumber = models.PositiveSmallIntegerField(
|
||||
null=False,
|
||||
default=date.today().year,
|
||||
verbose_name=_("Course Number")
|
||||
)
|
||||
|
||||
year = models.PositiveSmallIntegerField(
|
||||
null=False,
|
||||
default=date.today().year,
|
||||
verbose_name=_("Year")
|
||||
)
|
||||
|
||||
color = ColorField (
|
||||
samples=COLOR_PALETTE,
|
||||
verbose_name=_("Binder Color")
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.ctype}-{self.cnumber}"
|
||||
|
||||
103
cntmanage/flightslot/models/hourbuildings.py
Normal file
103
cntmanage/flightslot/models/hourbuildings.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
from datetime import timedelta
|
||||
|
||||
from ..models.weekpref import WeekPreference
|
||||
from ..models.aircrafts import AircraftTypes
|
||||
|
||||
|
||||
class HourBuilding(models.Model):
|
||||
id = models.BigAutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
weekpref = models.ForeignKey(
|
||||
WeekPreference,
|
||||
null=False,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
aircraft = models.CharField(
|
||||
null=False,
|
||||
choices=AircraftTypes
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
notes = models.TextField(
|
||||
max_length=140,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class HourBuildingLeg(models.Model):
|
||||
id = models.BigAutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
hb = models.ForeignKey(
|
||||
HourBuilding,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
departure = models.CharField(
|
||||
null=False,
|
||||
blank=False,
|
||||
default="LILV",
|
||||
max_length=4
|
||||
)
|
||||
|
||||
destination = models.CharField(
|
||||
null=False,
|
||||
blank=False,
|
||||
default="LILV",
|
||||
max_length=4
|
||||
)
|
||||
|
||||
time = models.DurationField(
|
||||
null=False,
|
||||
default = timedelta(hours=1)
|
||||
)
|
||||
|
||||
stop = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
if self.stop:
|
||||
return "Refuelling Stop"
|
||||
else:
|
||||
return f"Flight Leg: {self.departure} -> {self.destination}"
|
||||
109
cntmanage/flightslot/models/missions.py
Normal file
109
cntmanage/flightslot/models/missions.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db import models
|
||||
from datetime import timedelta
|
||||
|
||||
from ..models.weekpref import WeekPreference
|
||||
|
||||
class MissionType(models.TextChoices):
|
||||
OTHER = "OTHER", _("OTHER")
|
||||
PPL = "PPL", _("PPL")
|
||||
IR = "IR", _("IR")
|
||||
MEP = "MEP", _("MEP")
|
||||
CPL = "CPL", _("CPL")
|
||||
FI = "FI", _("FI")
|
||||
PC = "PC", _("PC")
|
||||
CHK = "CHK", _("CHK_6M")
|
||||
|
||||
class MissionProfile(models.Model):
|
||||
id = models.AutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
mtype = models.CharField(
|
||||
null=False,
|
||||
default=MissionType.PPL,
|
||||
choices=MissionType,
|
||||
verbose_name="Mission Type"
|
||||
)
|
||||
|
||||
mnum = models.PositiveSmallIntegerField(
|
||||
null=True,
|
||||
default=0,
|
||||
verbose_name="Mission Number"
|
||||
)
|
||||
|
||||
duration = models.DurationField(
|
||||
null=False,
|
||||
default=timedelta(hours=1)
|
||||
)
|
||||
|
||||
notes = models.TextField(
|
||||
max_length=140,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.mtype} {self.mnum}"
|
||||
|
||||
class Training(models.Model):
|
||||
id = models.BigAutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
weekpref = models.ForeignKey(
|
||||
WeekPreference,
|
||||
null=False,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
mission = models.ForeignKey(
|
||||
MissionProfile,
|
||||
null=True,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
notes = models.TextField(
|
||||
max_length=140,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.mission}"
|
||||
|
||||
80
cntmanage/flightslot/models/students.py
Normal file
80
cntmanage/flightslot/models/students.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from ..models.courses import Course
|
||||
|
||||
class Student(models.Model):
|
||||
id = models.AutoField(
|
||||
primary_key=True
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
null=False,
|
||||
db_index=True
|
||||
)
|
||||
|
||||
phone = models.CharField(
|
||||
null=True,
|
||||
max_length=16
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
null=False,
|
||||
max_length=32
|
||||
)
|
||||
|
||||
surname = models.CharField(
|
||||
null=False,
|
||||
max_length=32
|
||||
)
|
||||
|
||||
course = models.ForeignKey(
|
||||
Course,
|
||||
on_delete=models.DO_NOTHING,
|
||||
null=True
|
||||
)
|
||||
|
||||
active = models.BooleanField(
|
||||
null=False,
|
||||
default=True
|
||||
)
|
||||
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
def default_password(self) -> str:
|
||||
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id}"
|
||||
|
||||
# Override save method to add user for login upon Student creation
|
||||
def save(self, *args, **kwargs):
|
||||
creating = self.pk is None
|
||||
super().save(*args, **kwargs)
|
||||
if creating and not self.user:
|
||||
username = f"{self.name.lower()}.{self.surname.lower()}"
|
||||
# Avoid username conflict with progressive number
|
||||
base_username = username
|
||||
counter = 1
|
||||
while User.objects.filter(username=username).exists():
|
||||
username = f"{base_username}{counter}"
|
||||
counter += 1
|
||||
# Create user
|
||||
user = User.objects.create_user(
|
||||
first_name=self.name,
|
||||
last_name=self.surname,
|
||||
username=username,
|
||||
email=self.email,
|
||||
password=self.default_password(),
|
||||
is_staff=True
|
||||
)
|
||||
|
||||
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
||||
user.groups.add(student_group)
|
||||
self.user = user
|
||||
self.save()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.surname} {self.name[0]}. => {self.course}"
|
||||
28
cntmanage/flightslot/models/weekpref.py
Normal file
28
cntmanage/flightslot/models/weekpref.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.db import models
|
||||
from datetime import date
|
||||
|
||||
from ..models.students import Student
|
||||
|
||||
class WeekPreference(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"
|
||||
)
|
||||
|
||||
student = models.ForeignKey(
|
||||
Student,
|
||||
null=False,
|
||||
db_index=True,
|
||||
on_delete=models.DO_NOTHING,
|
||||
verbose_name="Student Selection"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"Week {self.week} - {self.student.surname} {self.student.name[0]}."
|
||||
3
cntmanage/flightslot/tests.py
Normal file
3
cntmanage/flightslot/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
cntmanage/flightslot/views.py
Normal file
3
cntmanage/flightslot/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Reference in New Issue
Block a user