Improve django admin

This commit is contained in:
Daniel Egger 2023-08-23 17:21:14 +02:00
parent 584aee1829
commit 9f8686e592
14 changed files with 294 additions and 22 deletions

View File

@ -1,4 +1,5 @@
import type { Dayjs } from "dayjs"; import type { Dayjs } from "dayjs";
import i18next from "i18next";
export const formatDate = (start: Dayjs, end: Dayjs) => { export const formatDate = (start: Dayjs, end: Dayjs) => {
const startDateString = getDateString(start); const startDateString = getDateString(start);
@ -7,7 +8,7 @@ export const formatDate = (start: Dayjs, end: Dayjs) => {
// if start isundefined, dont show the day twice // if start isundefined, dont show the day twice
if (!start.isValid() && !end.isValid()) { if (!start.isValid() && !end.isValid()) {
return "Termin nicht festgelegt"; return i18next.t("Termin nicht festgelegt");
} }
if (!start || (!start.isValid() && end.isValid())) { if (!start || (!start.isValid() && end.isValid())) {

View File

@ -24,7 +24,7 @@
"assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.", "assignmentSubmitted": "Du hast deine Ergebnisse erfolgreich abgegeben.",
"confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.", "confirmSubmitPerson": "Hiermit bestätige ich, dass die folgende Person meine Ergebnisse bewerten soll.",
"confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.", "confirmSubmitResults": "Hiermit bestätige ich, dass ich die Zusammenfassung meiner Ergebnisse überprüft habe und so abgeben will.",
"dueDateEvaluation": "assignment.dueDateEvaluation", "dueDateEvaluation": "Freigabetermin Bewertung",
"dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am: ", "dueDateIntroduction": "Reiche deine Ergebnisse pünktlich ein bis am: ",
"dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung", "dueDateNotSet": "Keine Abgabedaten wurden erfasst für diese Durchführung",
"dueDateSubmission": "Abgabetermin", "dueDateSubmission": "Abgabetermin",
@ -109,7 +109,7 @@
"showAllDueDates": "Alle Termine anzeigen" "showAllDueDates": "Alle Termine anzeigen"
}, },
"edoniqTest": { "edoniqTest": {
"qualifiesForExtendedTime": "edoniqTest.qualifiesForExtendedTime" "qualifiesForExtendedTime": "Ich habe Anrecht auf einen Nachteilsausgleich"
}, },
"feedback": { "feedback": {
"answers": "Antworten", "answers": "Antworten",
@ -206,6 +206,7 @@
"attendanceCourse": "Präsenzkurs", "attendanceCourse": "Präsenzkurs",
"casework": "Geleitete Fallarbeit", "casework": "Geleitete Fallarbeit",
"documents": "Dokumente", "documents": "Dokumente",
"edoniqTest": "Wissens- und Verständnisfragen",
"feedback": "Feedback", "feedback": "Feedback",
"learningModule": "Lernmodul", "learningModule": "Lernmodul",
"placeholder": "In Umsetzung", "placeholder": "In Umsetzung",
@ -274,7 +275,7 @@
"selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen.", "selfEvaluationNo": "@:selfEvaluation: Muss ich nochmals anschauen.",
"selfEvaluationYes": "@:selfEvaluation: Ich kann das.", "selfEvaluationYes": "@:selfEvaluation: Ich kann das.",
"steps": "Schritt {{current}} von {{max}}", "steps": "Schritt {{current}} von {{max}}",
"title": "@:selfEvaluation.selfEvaluation {{title}}", "title": "Selbsteinschätzung {{title}}",
"yes": "Ja, ich kann das" "yes": "Ja, ich kann das"
}, },
"settings": { "settings": {

View File

@ -275,7 +275,7 @@
"selfEvaluationNo": "@:selfEvaluation: Il faut que je regarde cela encore une fois de plus près.", "selfEvaluationNo": "@:selfEvaluation: Il faut que je regarde cela encore une fois de plus près.",
"selfEvaluationYes": "@:selfEvaluation: Je maîtrise cette question.", "selfEvaluationYes": "@:selfEvaluation: Je maîtrise cette question.",
"steps": "Étape {{current}} sur {{max}}", "steps": "Étape {{current}} sur {{max}}",
"title": "@:selfEvaluation.selfEvaluation {{title}}", "title": "Selbsteinschätzung {{title}}",
"yes": "Oui, je maîtrise cette question" "yes": "Oui, je maîtrise cette question"
}, },
"settings": { "settings": {

View File

@ -275,7 +275,7 @@
"selfEvaluationNo": "@:selfEvaluation: Devo riguardarlo ancora una volta.", "selfEvaluationNo": "@:selfEvaluation: Devo riguardarlo ancora una volta.",
"selfEvaluationYes": "@:selfEvaluation: Ho compreso tutto.", "selfEvaluationYes": "@:selfEvaluation: Ho compreso tutto.",
"steps": "Passo {{current}} di {{max}}", "steps": "Passo {{current}} di {{max}}",
"title": "@:selfEvaluation.selfEvaluation {{title}}", "title": "Selbsteinschätzung {{title}}",
"yes": "Sì, ho compreso tutto" "yes": "Sì, ho compreso tutto"
}, },
"settings": { "settings": {

View File

@ -33,6 +33,8 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
date_hierarchy = "created_at" date_hierarchy = "created_at"
list_display = [ list_display = [
"user", "user",
"user_last_name",
"user_first_name",
"course_session", "course_session",
"role", "role",
"created_at", "created_at",
@ -45,13 +47,25 @@ class CourseSessionUserAdmin(admin.ModelAdmin):
"course_session__title", "course_session__title",
] ]
list_filter = [ list_filter = [
"course_session",
"role", "role",
"course_session",
] ]
raw_id_fields = [ raw_id_fields = [
"user", "user",
] ]
def user_first_name(self, obj):
return obj.user.first_name
user_first_name.short_description = "First Name"
user_first_name.admin_order_field = "user__first_name"
def user_last_name(self, obj):
return obj.user.last_name
user_last_name.short_description = "Last Name"
user_last_name.admin_order_field = "user__last_name"
fieldsets = [ fieldsets = [
(None, {"fields": ("user", "course_session", "role")}), (None, {"fields": ("user", "course_session", "role")}),
( (

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.20 on 2023-08-23 15:44
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("course", "0003_alter_coursecompletion_additional_json_data"),
]
operations = [
migrations.AlterModelOptions(
name="course",
options={},
),
migrations.AlterModelOptions(
name="coursesession",
options={"ordering": ["title"]},
),
migrations.AlterModelOptions(
name="coursesessionuser",
options={
"ordering": ["user__last_name", "user__first_name", "user__email"]
},
),
]

View File

@ -24,9 +24,6 @@ class Course(models.Model):
_("Slug"), max_length=255, unique=True, blank=True, allow_unicode=True _("Slug"), max_length=255, unique=True, blank=True, allow_unicode=True
) )
class Meta:
verbose_name = _("Lehrgang")
def get_course_url(self): def get_course_url(self):
return f"/course/{self.slug}" return f"/course/{self.slug}"
@ -245,6 +242,9 @@ class CourseSession(models.Model):
def __str__(self): def __str__(self):
return f"{self.title}" return f"{self.title}"
class Meta:
ordering = ["title"]
class CourseSessionUser(models.Model): class CourseSessionUser(models.Model):
""" """
@ -280,6 +280,7 @@ class CourseSessionUser(models.Model):
name="course_session_user_unique_course_session_user", name="course_session_user_unique_course_session_user",
) )
] ]
ordering = ["user__last_name", "user__first_name", "user__email"]
def to_dict(self): def to_dict(self):
return { return {

View File

@ -1,9 +1,12 @@
from django import forms
from django.contrib import admin from django.contrib import admin
from vbv_lernwelt.course_session.models import ( from vbv_lernwelt.course_session.models import (
CourseSessionAssignment, CourseSessionAssignment,
CourseSessionAttendanceCourse, CourseSessionAttendanceCourse,
CourseSessionEdoniqTest,
) )
from vbv_lernwelt.learnpath.models import Circle
@admin.register(CourseSessionAttendanceCourse) @admin.register(CourseSessionAttendanceCourse)
@ -16,11 +19,59 @@ class CourseSessionAttendanceCourseAdmin(admin.ModelAdmin):
] ]
list_display = [ list_display = [
"course_session", "course_session",
"circle",
"learning_content", "learning_content",
"start_date",
"end_date",
"trainer", "trainer",
] ]
list_filter = ["course_session__course", "course_session"] list_filter = ["course_session__course", "course_session"]
def start_date(self, obj):
return obj.due_date.start
start_date.admin_order_field = "due_date__start"
def end_date(self, obj):
return obj.due_date.end
end_date.admin_order_field = "due_date__end"
def circle(self, obj):
try:
return obj.learning_content.get_ancestors().exact_type(Circle).first().title
except Exception:
# noop
pass
return None
# Create a method that serves as a form field
def circle_display(self, obj=None):
if obj:
return self.circle(obj)
return None
circle_display.short_description = "Circle"
# Make circle display-only in the form
def get_readonly_fields(self, request, obj=None):
readonly_fields = super(
CourseSessionAttendanceCourseAdmin, self
).get_readonly_fields(request, obj)
return readonly_fields + ["circle_display"]
# Override get_form to include circle_display
def get_form(self, request, obj=None, **kwargs):
form = super(CourseSessionAttendanceCourseAdmin, self).get_form(
request, obj, **kwargs
)
form.base_fields["circle_display"] = forms.CharField(
required=False,
label="Circle",
widget=forms.TextInput(attrs={"readonly": "readonly"}),
)
return form
@admin.register(CourseSessionAssignment) @admin.register(CourseSessionAssignment)
class CourseSessionAssignmentAdmin(admin.ModelAdmin): class CourseSessionAssignmentAdmin(admin.ModelAdmin):
@ -32,6 +83,116 @@ class CourseSessionAssignmentAdmin(admin.ModelAdmin):
] ]
list_display = [ list_display = [
"course_session", "course_session",
"circle",
"learning_content", "learning_content",
"submission_date",
"evaluation_date",
] ]
list_filter = ["course_session__course"] list_filter = ["course_session__course", "course_session"]
def submission_date(self, obj):
if obj.submission_deadline:
return obj.submission_deadline.start
return None
submission_date.admin_order_field = "submission_deadline__start"
def evaluation_date(self, obj):
if obj.evaluation_deadline:
return obj.evaluation_deadline.start
return None
evaluation_date.admin_order_field = "evaluation_deadline__start"
def circle(self, obj):
try:
return obj.learning_content.get_ancestors().exact_type(Circle).first().title
except Exception:
# noop
pass
return None
# Create a method that serves as a form field
def circle_display(self, obj=None):
if obj:
return self.circle(obj)
return None
circle_display.short_description = "Circle"
# Make circle display-only in the form
def get_readonly_fields(self, request, obj=None):
readonly_fields = super(CourseSessionAssignmentAdmin, self).get_readonly_fields(
request, obj
)
return readonly_fields + ["circle_display"]
# Override get_form to include circle_display
def get_form(self, request, obj=None, **kwargs):
form = super(CourseSessionAssignmentAdmin, self).get_form(
request, obj, **kwargs
)
form.base_fields["circle_display"] = forms.CharField(
required=False,
label="Circle",
widget=forms.TextInput(attrs={"readonly": "readonly"}),
)
return form
@admin.register(CourseSessionEdoniqTest)
class CourseSessionEdoniqTestAdmin(admin.ModelAdmin):
readonly_fields = [
"course_session",
"learning_content",
"deadline",
]
list_display = [
"course_session",
"circle",
"learning_content",
"deadline_date",
]
list_filter = ["course_session__course", "course_session"]
def deadline_date(self, obj):
if obj.deadline:
return obj.deadline.start
return None
deadline_date.admin_order_field = "deadline__start"
def circle(self, obj):
try:
return obj.learning_content.get_ancestors().exact_type(Circle).first().title
except Exception:
# noop
pass
return None
# Create a method that serves as a form field
def circle_display(self, obj=None):
if obj:
return self.circle(obj)
return None
circle_display.short_description = "Circle"
# Make circle display-only in the form
def get_readonly_fields(self, request, obj=None):
readonly_fields = super(CourseSessionEdoniqTestAdmin, self).get_readonly_fields(
request, obj
)
return readonly_fields + ["circle_display"]
# Override get_form to include circle_display
def get_form(self, request, obj=None, **kwargs):
form = super(CourseSessionEdoniqTestAdmin, self).get_form(
request, obj, **kwargs
)
form.base_fields["circle_display"] = forms.CharField(
required=False,
label="Circle",
widget=forms.TextInput(attrs={"readonly": "readonly"}),
)
return form

View File

@ -29,6 +29,9 @@ class CourseSessionAttendanceCourse(models.Model):
location = models.CharField(max_length=255, blank=True, default="") location = models.CharField(max_length=255, blank=True, default="")
trainer = models.CharField(max_length=255, blank=True, default="") trainer = models.CharField(max_length=255, blank=True, default="")
class Meta:
ordering = ["course_session", "due_date__start"]
# because the attendance list is more of a snapshot of the current state # because the attendance list is more of a snapshot of the current state
# we will store the attendance list as a JSONField # we will store the attendance list as a JSONField
# the important field of the list type is "user_id" # the important field of the list type is "user_id"
@ -103,6 +106,9 @@ class CourseSessionAssignment(models.Model):
null=True, null=True,
) )
class Meta:
ordering = ["course_session", "submission_deadline__start"]
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.learning_content_id: if self.learning_content_id:
title = self.learning_content.title title = self.learning_content.title
@ -178,6 +184,9 @@ class CourseSessionEdoniqTest(models.Model):
null=True, null=True,
) )
class Meta:
ordering = ["course_session", "deadline__start"]
def __str__(self): def __str__(self):
return f"{self.course_session} - {self.learning_content}" return f"{self.course_session} - {self.learning_content}"

View File

@ -3,6 +3,7 @@ from wagtail.models import Page
from vbv_lernwelt.duedate.models import DueDate from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.learnpath.models import ( from vbv_lernwelt.learnpath.models import (
Circle,
LearningContentAttendanceCourse, LearningContentAttendanceCourse,
LearningContentEdoniqTest, LearningContentEdoniqTest,
) )
@ -14,12 +15,13 @@ class DueDateAdmin(admin.ModelAdmin):
date_hierarchy = "start" date_hierarchy = "start"
list_display = [ list_display = [
"course_session", "course_session",
"circle",
"title", "title",
"display_subtitle", "display_subtitle",
"start", "start",
"end", "end",
] ]
list_filter = ["course_session"] list_filter = ["course_session__course", "course_session"]
readonly_fields = ["course_session", "page"] readonly_fields = ["course_session", "page"]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
@ -30,6 +32,7 @@ class DueDateAdmin(admin.ModelAdmin):
if not obj.manual_override_fields: if not obj.manual_override_fields:
return default_readonly + [ return default_readonly + [
"circle",
"title", "title",
"subtitle", "subtitle",
"assignment_type_translation_key", "assignment_type_translation_key",
@ -39,6 +42,22 @@ class DueDateAdmin(admin.ModelAdmin):
return default_readonly return default_readonly
def circle(self, obj):
try:
return obj.page.get_ancestors().exact_type(Circle).first().title
except Exception:
# noop
pass
return None
# Create a method that serves as a form field
def circle_display(self, obj=None):
if obj:
return self.circle(obj)
return None
circle_display.short_description = "Circle"
def formfield_for_foreignkey(self, db_field, request, **kwargs): def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "page": if db_field.name == "page":
if request.resolver_match.kwargs.get("object_id"): if request.resolver_match.kwargs.get("object_id"):

View File

@ -60,12 +60,16 @@ class DueDate(models.Model):
def __str__(self): def __str__(self):
if self.is_undefined: if self.is_undefined:
return f"DueDate: {self.title} undefined" return f"{self.title} undefined"
start_str = self.start.strftime("%Y-%m-%d %H:%M") if self.start else "-"
result = f"DueDate: {self.title} {start_str}" # Convert the UTC datetime to local time
local_start = timezone.localtime(self.start) if self.start else None
start_str = local_start.strftime("%Y-%m-%d %H:%M") if local_start else "-"
result = f"{self.title} {start_str}"
if self.end: if self.end:
end_str = self.end.strftime("%Y-%m-%d %H:%M") if self.end else "-" local_end = timezone.localtime(self.end) if self.end else None
end_str = local_end.strftime("%Y-%m-%d %H:%M") if local_end else "-"
result += f" - {end_str}" result += f" - {end_str}"
return result return result

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.20 on 2023-08-23 15:44
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("course", "0004_auto_20230823_1744"),
("feedback", "0002_initial"),
]
operations = [
migrations.AlterField(
model_name="feedbackresponse",
name="course_session",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="course.coursesession"
),
),
]

View File

@ -62,4 +62,4 @@ class FeedbackResponse(models.Model):
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
circle = models.ForeignKey("learnpath.Circle", models.PROTECT) circle = models.ForeignKey("learnpath.Circle", models.PROTECT)
course_session = models.ForeignKey("course.CourseSession", models.PROTECT) course_session = models.ForeignKey("course.CourseSession", models.CASCADE)

View File

@ -141,6 +141,7 @@ LP_DATA = {
"slug": "ménage-partie-1", "slug": "ménage-partie-1",
"presence_course": "ménage-partie-1-lc-cours-de-présence-ménage-partie-1", "presence_course": "ménage-partie-1-lc-cours-de-présence-ménage-partie-1",
"assignments": [], "assignments": [],
"edoniq_tests": [],
}, },
"it": { "it": {
"title": "Economica domestica parte 1", "title": "Economica domestica parte 1",
@ -163,6 +164,7 @@ LP_DATA = {
"slug": "ménage-partie-2", "slug": "ménage-partie-2",
"presence_course": "ménage-partie-2-lc-cours-de-présence-ménage-partie-2", "presence_course": "ménage-partie-2-lc-cours-de-présence-ménage-partie-2",
"assignments": [], "assignments": [],
"edoniq_tests": [],
}, },
"it": { "it": {
"title": "Economica domestica parte 2", "title": "Economica domestica parte 2",
@ -281,7 +283,16 @@ def import_course_sessions_from_excel(
course = get_uk_course(language) course = get_uk_course(language)
create_or_update_course_session( create_or_update_course_session(
course, data, language, circle_keys=["Kickoff", "Basis", "Fahrzeug"] course,
data,
language,
circle_keys=[
"Kickoff",
"Basis",
"Fahrzeug",
"Haushalt Teil 1",
"Haushalt Teil 2",
],
) )
@ -315,7 +326,7 @@ def create_or_update_course_session(
title = f"{region} {generation} {group}" title = f"{region} {generation} {group}"
cs, _created = CourseSession.objects.get_or_create( cs, _created = CourseSession.objects.get_or_create(
title=title, course=course, import_id=import_id course=course, import_id=import_id
) )
cs.additional_json_data["import_data"] = data cs.additional_json_data["import_data"] = data
@ -360,7 +371,7 @@ def create_or_update_course_session(
for assignment_slug in circle_data["assignments"]: for assignment_slug in circle_data["assignments"]:
create_or_update_course_session_assignment( create_or_update_course_session_assignment(
cs, course.slug, assignment_slug, presence_day_start, presence_day_end cs, course.slug, assignment_slug, presence_day_start
) )
for test_slug in circle_data["edoniq_tests"]: for test_slug in circle_data["edoniq_tests"]:
@ -415,7 +426,6 @@ def create_or_update_course_session_assignment(
course_slug: str, course_slug: str,
assignment_slug: str, assignment_slug: str,
start: datetime, start: datetime,
end: datetime,
): ):
logger.debug("import", slug=f"{course_slug}-lp-circle-{assignment_slug}") logger.debug("import", slug=f"{course_slug}-lp-circle-{assignment_slug}")
@ -443,13 +453,18 @@ def create_or_update_course_session_assignment(
elif ( elif (
csa.learning_content.assignment_type == AssignmentType.CASEWORK.value csa.learning_content.assignment_type == AssignmentType.CASEWORK.value
and end and start
): ):
csa.submission_deadline.start = timezone.make_aware( csa.submission_deadline.start = timezone.make_aware(
start start
) + timezone.timedelta(days=30) ) + timezone.timedelta(days=30)
csa.submission_deadline.end = None csa.submission_deadline.end = None
csa.submission_deadline.save() csa.submission_deadline.save()
csa.evaluation_deadline.start = timezone.make_aware(
start
) + timezone.timedelta(days=45)
csa.evaluation_deadline.end = None
csa.evaluation_deadline.save()
def create_or_update_course_session_edoniq_test( def create_or_update_course_session_edoniq_test(