From bb9ff3a86ce11dcbde7481939788abc4864e635b Mon Sep 17 00:00:00 2001 From: Emanuele Date: Fri, 14 Nov 2025 15:47:18 +0100 Subject: [PATCH] Demo export action and added color badges where needed --- techdb/flightslot/admin.py | 87 ++++++++++++++++--- techdb/flightslot/custom/colortag.py | 18 ++++ techdb/flightslot/custom/defpassword.py | 4 + ...rse_cnumber_alter_course_color_and_more.py | 39 +++++++++ techdb/flightslot/models/aircrafts.py | 2 +- techdb/flightslot/signals.py | 20 ++--- 6 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 techdb/flightslot/custom/colortag.py create mode 100644 techdb/flightslot/custom/defpassword.py create mode 100644 techdb/flightslot/migrations/0016_alter_course_cnumber_alter_course_color_and_more.py diff --git a/techdb/flightslot/admin.py b/techdb/flightslot/admin.py index cfcb9f7..c98b1a9 100644 --- a/techdb/flightslot/admin.py +++ b/techdb/flightslot/admin.py @@ -1,8 +1,13 @@ from django import forms +from django.db.models.query import QuerySet from django.contrib import admin +from django.http import HttpRequest, HttpResponse +from django.utils.safestring import SafeText from durationwidget.widgets import TimeDurationWidget from datetime import date + import nested_admin +import csv from .models.courses import Course from .models.hourbuildings import HourBuilding, HourBuildingLeg @@ -10,6 +15,9 @@ from .models.missions import Training, MissionProfile from .models.students import Student from .models.weekpref import WeekPreference +from .custom.colortag import course_color +from .custom.defpassword import default_password + class TrainingForm(forms.ModelForm): model=Training @@ -49,7 +57,43 @@ class TrainingInLIne(nested_admin.NestedTabularInline): class WeekPreferenceAdmin(nested_admin.NestedModelAdmin): inlines = [TrainingInLIne, HourBuildingInLine] - list_filter = ["week", "student__course", "student"] + list_display = ("week", "student__name", "student__surname", "student__course", "course_color", "student_brief_mix") + list_filter = ("week", "student__course", "student") + actions = ("export_selected",) + + @admin.action(description="Export Selected Preferences") + def export_selected(modeladmin, request: HttpRequest, queryset: QuerySet[WeekPreference]) -> HttpResponse: + filename = "weekpreferences_export.csv" + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = f'attachment; filename="{filename}"' + + writer = csv.writer(response) + # intestazione — scegli i campi che vuoi esportare + writer.writerow(['student_name', 'student_surname', 'course', 'course_color', "training", "hourbuilding"]) + + for q in queryset: + writer.writerow([ + q.student.name, + q.student.surname, + q.student.course, + q.student.course.color, + "+".join([f"{t.mission.mtype}-{t.mission.mnum}" for t in Training.objects.filter(weekpref = q.id)]), + "+".join([f"HB_{t.aircraft}" for t in HourBuilding.objects.filter(weekpref = q.id)]), + ]) + return response + + @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) def has_module_permission(self, request): if hasattr(request.user, 'student'): @@ -73,13 +117,13 @@ class WeekPreferenceAdmin(nested_admin.NestedModelAdmin): if 'student' in form.base_fields: form.base_fields['student'].initial = student form.base_fields['student'].disabled = True + form.base_fields['week'].disabled = True # If form contains the week field if 'week' in form.base_fields: # Set default value as current week current_week = date.today().isocalendar().week form.base_fields['week'].initial = current_week - form.base_fields['week'].disabled = True return form def save_model(self, request, obj, form, change): @@ -89,22 +133,41 @@ class WeekPreferenceAdmin(nested_admin.NestedModelAdmin): super().save_model(request, obj, form, change) class StudentAdmin(admin.ModelAdmin): - list_display = ("surname", "name", "course", "email","active") - list_filter = ["course", "active"] + list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active") + list_filter = ("course", "active") + actions = ("disable_students",) -class CourseAdminForm(forms.ModelForm): - class Meta: - model = Course + @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(default_password(student=obj)) + + @admin.action(description="Disable Students") + def disable_students(modeladmin, request: HttpRequest, queryset: QuerySet[Student]): + queryset.update(active = False) + pass + class CourseAdmin(admin.ModelAdmin): - list_display = ["ctype", "cnumber", "year", "color"] - list_filter = ["ctype", "year"] - form=CourseAdminForm + 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) class MissionProfileAdmin(admin.ModelAdmin): - list_display = ("mtype", "mnum") + list_display = ("mtype", "mnum",) + list_filter = ("mtype",) admin.site.register(Course, CourseAdmin) -admin.site.register(MissionProfile) +admin.site.register(MissionProfile, MissionProfileAdmin) admin.site.register(Student, StudentAdmin) admin.site.register(WeekPreference, WeekPreferenceAdmin) diff --git a/techdb/flightslot/custom/colortag.py b/techdb/flightslot/custom/colortag.py new file mode 100644 index 0000000..c6cf577 --- /dev/null +++ b/techdb/flightslot/custom/colortag.py @@ -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( + '
', + color + ) \ No newline at end of file diff --git a/techdb/flightslot/custom/defpassword.py b/techdb/flightslot/custom/defpassword.py new file mode 100644 index 0000000..97aedff --- /dev/null +++ b/techdb/flightslot/custom/defpassword.py @@ -0,0 +1,4 @@ +from ..models.students import Student + +def default_password(student: Student) -> str: + return f"{student.name.lower()[0]}{student.surname.lower()}{student.id}" diff --git a/techdb/flightslot/migrations/0016_alter_course_cnumber_alter_course_color_and_more.py b/techdb/flightslot/migrations/0016_alter_course_cnumber_alter_course_color_and_more.py new file mode 100644 index 0000000..cf635ad --- /dev/null +++ b/techdb/flightslot/migrations/0016_alter_course_cnumber_alter_course_color_and_more.py @@ -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')]), + ), + ] diff --git a/techdb/flightslot/models/aircrafts.py b/techdb/flightslot/models/aircrafts.py index c452a18..4afb9c2 100644 --- a/techdb/flightslot/models/aircrafts.py +++ b/techdb/flightslot/models/aircrafts.py @@ -6,4 +6,4 @@ class AircraftTypes(models.TextChoices): P208 = "P208", _("Tecnam P2008") PA28 = "PA28", _("Piper PA28R") C182 = "C182", _("Cessna 182Q") - P210 = "P210", _("Tecnam P2010") + P210 = "TWEN", _("Tecnam P2010") diff --git a/techdb/flightslot/signals.py b/techdb/flightslot/signals.py index 46cc6b5..39433e9 100644 --- a/techdb/flightslot/signals.py +++ b/techdb/flightslot/signals.py @@ -3,32 +3,28 @@ from django.dispatch import receiver from django.contrib.auth.models import User from django.contrib.auth.models import Group from .models.students import Student +from .custom.defpassword import default_password # Create a Django user every time a new student is created @receiver(post_save, sender=Student) -def create_user_for_student(sender: Student, instance: Student, created, **kwargs): - if created and not instance.user: - print("_____ SAVING USER _____") - username = f"{instance.name.lower()}.{instance.surname.lower()}" +def create_user_for_student(sender: Student, student: Student, created, **kwargs): + if created and not student.user: + username = f"{student.name.lower()}.{student.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 - # Generate standard password for every student - password = f"{instance.name.lower()[0]}{instance.surname.lower()}{instance.id}" # Create user user = User.objects.create_user( username=username, - email=instance.email, - password=password + email=student.email, + password=default_password(student=student) ) student_group, _ = Group.objects.get_or_create(name="StudentGroup") user.groups.add(student_group) - - print(f"User: {user.username}\tPassword: {password}") - instance.user = user - instance.save() + student.user = user + student.save()