Demo export action and added color badges where needed
This commit is contained in:
@@ -1,8 +1,13 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.http import HttpRequest, HttpResponse
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
from durationwidget.widgets import TimeDurationWidget
|
from durationwidget.widgets import TimeDurationWidget
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
import nested_admin
|
import nested_admin
|
||||||
|
import csv
|
||||||
|
|
||||||
from .models.courses import Course
|
from .models.courses import Course
|
||||||
from .models.hourbuildings import HourBuilding, HourBuildingLeg
|
from .models.hourbuildings import HourBuilding, HourBuildingLeg
|
||||||
@@ -10,6 +15,9 @@ from .models.missions import Training, MissionProfile
|
|||||||
from .models.students import Student
|
from .models.students import Student
|
||||||
from .models.weekpref import WeekPreference
|
from .models.weekpref import WeekPreference
|
||||||
|
|
||||||
|
from .custom.colortag import course_color
|
||||||
|
from .custom.defpassword import default_password
|
||||||
|
|
||||||
class TrainingForm(forms.ModelForm):
|
class TrainingForm(forms.ModelForm):
|
||||||
model=Training
|
model=Training
|
||||||
|
|
||||||
@@ -49,7 +57,43 @@ class TrainingInLIne(nested_admin.NestedTabularInline):
|
|||||||
|
|
||||||
class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
||||||
inlines = [TrainingInLIne, HourBuildingInLine]
|
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):
|
def has_module_permission(self, request):
|
||||||
if hasattr(request.user, 'student'):
|
if hasattr(request.user, 'student'):
|
||||||
@@ -73,13 +117,13 @@ class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
|||||||
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
|
||||||
|
form.base_fields['week'].disabled = True
|
||||||
|
|
||||||
# If form contains the week field
|
# If form contains the week field
|
||||||
if 'week' in form.base_fields:
|
if 'week' in form.base_fields:
|
||||||
# Set default value as current week
|
# Set default value as current week
|
||||||
current_week = date.today().isocalendar().week
|
current_week = date.today().isocalendar().week
|
||||||
form.base_fields['week'].initial = current_week
|
form.base_fields['week'].initial = current_week
|
||||||
form.base_fields['week'].disabled = True
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
@@ -89,22 +133,41 @@ class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
|||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
class StudentAdmin(admin.ModelAdmin):
|
class StudentAdmin(admin.ModelAdmin):
|
||||||
list_display = ("surname", "name", "course", "email","active")
|
list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active")
|
||||||
list_filter = ["course", "active"]
|
list_filter = ("course", "active")
|
||||||
|
actions = ("disable_students",)
|
||||||
|
|
||||||
class CourseAdminForm(forms.ModelForm):
|
@admin.display(description="Color")
|
||||||
class Meta:
|
def course_color(self, obj: Student) -> SafeText:
|
||||||
model = Course
|
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):
|
class CourseAdmin(admin.ModelAdmin):
|
||||||
list_display = ["ctype", "cnumber", "year", "color"]
|
list_display = ("ctype", "cnumber","color_display", "year")
|
||||||
list_filter = ["ctype", "year"]
|
list_filter = ("ctype", "year")
|
||||||
form=CourseAdminForm
|
|
||||||
|
# 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):
|
class MissionProfileAdmin(admin.ModelAdmin):
|
||||||
list_display = ("mtype", "mnum")
|
list_display = ("mtype", "mnum",)
|
||||||
|
list_filter = ("mtype",)
|
||||||
|
|
||||||
admin.site.register(Course, CourseAdmin)
|
admin.site.register(Course, CourseAdmin)
|
||||||
admin.site.register(MissionProfile)
|
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)
|
||||||
|
|||||||
18
techdb/flightslot/custom/colortag.py
Normal file
18
techdb/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
|
||||||
|
)
|
||||||
4
techdb/flightslot/custom/defpassword.py
Normal file
4
techdb/flightslot/custom/defpassword.py
Normal file
@@ -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}"
|
||||||
@@ -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')]),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,4 +6,4 @@ class AircraftTypes(models.TextChoices):
|
|||||||
P208 = "P208", _("Tecnam P2008")
|
P208 = "P208", _("Tecnam P2008")
|
||||||
PA28 = "PA28", _("Piper PA28R")
|
PA28 = "PA28", _("Piper PA28R")
|
||||||
C182 = "C182", _("Cessna 182Q")
|
C182 = "C182", _("Cessna 182Q")
|
||||||
P210 = "P210", _("Tecnam P2010")
|
P210 = "TWEN", _("Tecnam P2010")
|
||||||
|
|||||||
@@ -3,32 +3,28 @@ from django.dispatch import receiver
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from .models.students import Student
|
from .models.students import Student
|
||||||
|
from .custom.defpassword import default_password
|
||||||
|
|
||||||
|
|
||||||
# Create a Django user every time a new student is created
|
# Create a Django user every time a new student is created
|
||||||
@receiver(post_save, sender=Student)
|
@receiver(post_save, sender=Student)
|
||||||
def create_user_for_student(sender: Student, instance: Student, created, **kwargs):
|
def create_user_for_student(sender: Student, student: Student, created, **kwargs):
|
||||||
if created and not instance.user:
|
if created and not student.user:
|
||||||
print("_____ SAVING USER _____")
|
username = f"{student.name.lower()}.{student.surname.lower()}"
|
||||||
username = f"{instance.name.lower()}.{instance.surname.lower()}"
|
|
||||||
# Avoid username conflict with progressive number
|
# Avoid username conflict with progressive number
|
||||||
base_username = username
|
base_username = username
|
||||||
counter = 1
|
counter = 1
|
||||||
while User.objects.filter(username=username).exists():
|
while User.objects.filter(username=username).exists():
|
||||||
username = f"{base_username}{counter}"
|
username = f"{base_username}{counter}"
|
||||||
counter += 1
|
counter += 1
|
||||||
# Generate standard password for every student
|
|
||||||
password = f"{instance.name.lower()[0]}{instance.surname.lower()}{instance.id}"
|
|
||||||
# Create user
|
# Create user
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
username=username,
|
username=username,
|
||||||
email=instance.email,
|
email=student.email,
|
||||||
password=password
|
password=default_password(student=student)
|
||||||
)
|
)
|
||||||
|
|
||||||
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
||||||
user.groups.add(student_group)
|
user.groups.add(student_group)
|
||||||
|
student.user = user
|
||||||
print(f"User: {user.username}\tPassword: {password}")
|
student.save()
|
||||||
instance.user = user
|
|
||||||
instance.save()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user