|
8 | 8 | from django.shortcuts import render |
9 | 9 | from django.urls import path, reverse |
10 | 10 | from django.utils import timezone |
| 11 | +from django.utils.safestring import mark_safe |
11 | 12 | from django.utils.translation import gettext_lazy as _ |
12 | 13 | from django.utils.translation import ngettext |
13 | 14 |
|
14 | | -from applications.models import EmailTemplate, School, SummerVoucherConfiguration |
| 15 | +from applications.models import ( |
| 16 | + EmailTemplate, |
| 17 | + School, |
| 18 | + SummerVoucherConfiguration, |
| 19 | + YouthApplication, |
| 20 | + YouthSummerVoucher, |
| 21 | +) |
15 | 22 | from applications.services import EmailTemplateService |
16 | 23 | from applications.target_groups import get_target_group_choices |
17 | 24 |
|
@@ -250,7 +257,172 @@ def reinitialize_from_file(self, request, queryset): |
250 | 257 | ) |
251 | 258 |
|
252 | 259 |
|
| 260 | +class SchoolFilter(admin.SimpleListFilter): |
| 261 | + title = _("school") |
| 262 | + parameter_name = "school" |
| 263 | + |
| 264 | + def lookups(self, request, model_admin): |
| 265 | + schools = School.objects.values_list("name", flat=True).order_by("name") |
| 266 | + return [(school, school) for school in schools] |
| 267 | + |
| 268 | + def queryset(self, request, queryset): |
| 269 | + if self.value(): |
| 270 | + return queryset.filter(school=self.value()) |
| 271 | + return queryset |
| 272 | + |
| 273 | + |
| 274 | +class IsValidSchoolFilter(admin.SimpleListFilter): |
| 275 | + title = _("is in school list") |
| 276 | + parameter_name = "is_valid_school" |
| 277 | + |
| 278 | + def lookups(self, request, model_admin): |
| 279 | + return ( |
| 280 | + ("yes", _("Yes")), |
| 281 | + ("no", _("No")), |
| 282 | + ) |
| 283 | + |
| 284 | + def queryset(self, request, queryset): |
| 285 | + if self.value() == "yes": |
| 286 | + return queryset.filter( |
| 287 | + school__in=School.objects.values_list("name", flat=True) |
| 288 | + ) |
| 289 | + if self.value() == "no": |
| 290 | + return queryset.exclude( |
| 291 | + school__in=School.objects.values_list("name", flat=True) |
| 292 | + ) |
| 293 | + return queryset |
| 294 | + |
| 295 | + |
| 296 | +class YouthApplicationAdmin(admin.ModelAdmin): |
| 297 | + list_display = [ |
| 298 | + "id", |
| 299 | + "first_name", |
| 300 | + "last_name", |
| 301 | + "masked_social_security_number", |
| 302 | + "school", |
| 303 | + "is_valid_school", |
| 304 | + "status", |
| 305 | + "created_at", |
| 306 | + "modified_at", |
| 307 | + ] |
| 308 | + list_filter = [ |
| 309 | + "created_at", |
| 310 | + "modified_at", |
| 311 | + "status", |
| 312 | + IsValidSchoolFilter, |
| 313 | + SchoolFilter, |
| 314 | + ] |
| 315 | + date_hierarchy = "created_at" |
| 316 | + search_fields = [ |
| 317 | + "id", |
| 318 | + "first_name", |
| 319 | + "last_name", |
| 320 | + "school", |
| 321 | + ] |
| 322 | + |
| 323 | + # A custom field to list contact info fields |
| 324 | + # This is used to determine which fields should be readonly |
| 325 | + contact_info_fields = [ |
| 326 | + "first_name", |
| 327 | + "last_name", |
| 328 | + "email", |
| 329 | + "phone_number", |
| 330 | + "postcode", |
| 331 | + "school", |
| 332 | + "is_unlisted_school", |
| 333 | + "language", |
| 334 | + ] |
| 335 | + |
| 336 | + def is_valid_school(self, obj): |
| 337 | + return School.objects.filter(name=obj.school).exists() |
| 338 | + |
| 339 | + is_valid_school.boolean = True |
| 340 | + # The school list changes in time, so we can test only whether the value is |
| 341 | + # currently in school list. |
| 342 | + is_valid_school.short_description = _("is in current school list") |
| 343 | + |
| 344 | + def masked_social_security_number(self, obj): |
| 345 | + """Mask social security number for display.""" |
| 346 | + if obj.social_security_number: |
| 347 | + return "******" + obj.social_security_number[-4:] |
| 348 | + return "" |
| 349 | + |
| 350 | + masked_social_security_number.short_description = _("social security number") |
| 351 | + |
| 352 | + def get_readonly_fields(self, request, obj=None): |
| 353 | + """Make contact info fields readonly.""" |
| 354 | + if obj: |
| 355 | + return [ |
| 356 | + f.name |
| 357 | + for f in self.model._meta.fields |
| 358 | + if f.name not in self.contact_info_fields |
| 359 | + ] |
| 360 | + return super().get_readonly_fields(request, obj) |
| 361 | + |
| 362 | + def has_add_permission(self, request): |
| 363 | + """Disable adding new applications.""" |
| 364 | + return False |
| 365 | + |
| 366 | + def has_delete_permission(self, request, obj=None): |
| 367 | + """Disable deleting applications.""" |
| 368 | + return False |
| 369 | + |
| 370 | + |
| 371 | +class YouthSummerVoucherAdmin(admin.ModelAdmin): |
| 372 | + list_display = [ |
| 373 | + "id", |
| 374 | + "summer_voucher_serial_number", |
| 375 | + "target_group", |
| 376 | + "youth_application_link", |
| 377 | + "masked_social_security_number", |
| 378 | + "youth_application__email", |
| 379 | + "youth_application__phone_number", |
| 380 | + "created_at", |
| 381 | + "modified_at", |
| 382 | + ] |
| 383 | + list_filter = [ |
| 384 | + "target_group", |
| 385 | + "created_at", |
| 386 | + "modified_at", |
| 387 | + ] |
| 388 | + date_hierarchy = "created_at" |
| 389 | + search_fields = [ |
| 390 | + "youth_application__first_name", |
| 391 | + "youth_application__last_name", |
| 392 | + "youth_application__email", |
| 393 | + "youth_application__phone_number", |
| 394 | + "summer_voucher_serial_number", |
| 395 | + "id", |
| 396 | + ] |
| 397 | + autocomplete_fields = [ |
| 398 | + "youth_application", |
| 399 | + ] |
| 400 | + |
| 401 | + def queryset(self, request): |
| 402 | + return super().queryset(request).select_related("youth_application") |
| 403 | + |
| 404 | + def masked_social_security_number(self, obj): |
| 405 | + """Mask social security number for display.""" |
| 406 | + if obj.youth_application.social_security_number: |
| 407 | + return "******" + obj.youth_application.social_security_number[-4:] |
| 408 | + return "" |
| 409 | + |
| 410 | + masked_social_security_number.short_description = _("social security number") |
| 411 | + |
| 412 | + def youth_application_link(self, obj): |
| 413 | + url = reverse( |
| 414 | + "admin:applications_youthapplication_change", |
| 415 | + args=[obj.youth_application.id], |
| 416 | + ) |
| 417 | + link_text = obj.youth_application.name or obj.youth_application.email |
| 418 | + return mark_safe(f'<a href="{url}">{link_text}</a>') |
| 419 | + |
| 420 | + youth_application_link.short_description = _("youth application") |
| 421 | + |
| 422 | + |
253 | 423 | if apps.is_installed("django.contrib.admin"): |
254 | 424 | admin.site.register(SummerVoucherConfiguration, SummerVoucherConfigurationAdmin) |
255 | 425 | admin.site.register(School, SchoolAdmin) |
256 | 426 | admin.site.register(EmailTemplate, EmailTemplateAdmin) |
| 427 | + admin.site.register(YouthApplication, YouthApplicationAdmin) |
| 428 | + admin.site.register(YouthSummerVoucher, YouthSummerVoucherAdmin) |
0 commit comments