wip: Add competence certificate export
This commit is contained in:
parent
984513b3a2
commit
b16016b34c
|
|
@ -0,0 +1,18 @@
|
||||||
|
import djclick as click
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from vbv_lernwelt.assignment.services import export_competence_certificates
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("course_session_id")
|
||||||
|
@click.option(
|
||||||
|
"--save-as-file/--no-save-as-file",
|
||||||
|
default=True,
|
||||||
|
help="`save-as-file` to save the file, `no-save-as-file` returns bytes. Default is `save-as-file`.",
|
||||||
|
)
|
||||||
|
def command(course_session_id, save_as_file):
|
||||||
|
# using the output from call_command was a bit cumbersome, so this is just a wrapper for the actual function
|
||||||
|
export_competence_certificates([course_session_id], save_as_file)
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import structlog
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from openpyxl import Workbook
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from wagtail.models import Page
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
|
@ -14,10 +18,38 @@ from vbv_lernwelt.assignment.models import (
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.core.models import User
|
from vbv_lernwelt.core.models import User
|
||||||
from vbv_lernwelt.core.utils import find_first
|
from vbv_lernwelt.core.utils import find_first
|
||||||
from vbv_lernwelt.course.models import CourseCompletionStatus, CourseSession
|
from vbv_lernwelt.course.models import (
|
||||||
|
CourseCompletionStatus,
|
||||||
|
CourseSession,
|
||||||
|
CourseSessionUser,
|
||||||
|
)
|
||||||
from vbv_lernwelt.course.services import mark_course_completion
|
from vbv_lernwelt.course.services import mark_course_completion
|
||||||
|
from vbv_lernwelt.course_session.models import (
|
||||||
|
CourseSessionAssignment,
|
||||||
|
CourseSessionEdoniqTest,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.course_session.services.export import (
|
||||||
|
add_user_export_data,
|
||||||
|
add_user_headers,
|
||||||
|
get_ordered_csus_by_course_session,
|
||||||
|
group_by_session_title,
|
||||||
|
make_export_filename,
|
||||||
|
sanitize_sheet_name,
|
||||||
|
)
|
||||||
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
|
from vbv_lernwelt.learnpath.models import LearningContent
|
||||||
from vbv_lernwelt.notify.services import NotificationService
|
from vbv_lernwelt.notify.services import NotificationService
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CompetenceCertificateElement:
|
||||||
|
assignment: Assignment
|
||||||
|
date: DueDate
|
||||||
|
learning_content: LearningContent
|
||||||
|
course_session: CourseSession
|
||||||
|
|
||||||
|
|
||||||
def update_assignment_completion(
|
def update_assignment_completion(
|
||||||
assignment_user: User,
|
assignment_user: User,
|
||||||
|
|
@ -67,9 +99,9 @@ def update_assignment_completion(
|
||||||
assignment_user_id=assignment_user.id,
|
assignment_user_id=assignment_user.id,
|
||||||
assignment_id=assignment.id,
|
assignment_id=assignment.id,
|
||||||
course_session_id=course_session.id,
|
course_session_id=course_session.id,
|
||||||
learning_content_page_id=learning_content_page.id
|
learning_content_page_id=(
|
||||||
if learning_content_page
|
learning_content_page.id if learning_content_page else None
|
||||||
else None,
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if initialize_completion:
|
if initialize_completion:
|
||||||
|
|
@ -271,3 +303,192 @@ def _remove_unknown_entries(assignment, completion_data):
|
||||||
key: value for key, value in completion_data.items() if key in input_task_ids
|
key: value for key, value in completion_data.items() if key in input_task_ids
|
||||||
}
|
}
|
||||||
return filtered_completion_data
|
return filtered_completion_data
|
||||||
|
|
||||||
|
|
||||||
|
def export_competence_certificates(
|
||||||
|
course_session_ids: list[str], save_as_file: bool, circle_ids: list[int] = None
|
||||||
|
):
|
||||||
|
if len(course_session_ids) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
COMPETENCE_ASSIGNMENT_TYPES = [
|
||||||
|
AssignmentType.CASEWORK.value,
|
||||||
|
AssignmentType.EDONIQ_TEST.value,
|
||||||
|
]
|
||||||
|
|
||||||
|
wb = Workbook()
|
||||||
|
# remove the first sheet is just easier than keeping track of the active sheet
|
||||||
|
wb.remove(wb.active)
|
||||||
|
|
||||||
|
competence_certificate_elements = _get_competence_certificate_elements(
|
||||||
|
course_session_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
assignemnt_completions = AssignmentCompletion.objects.filter(
|
||||||
|
course_session_id__in=course_session_ids,
|
||||||
|
assignment__assignment_type__in=COMPETENCE_ASSIGNMENT_TYPES,
|
||||||
|
).order_by("course_session", "assignment")
|
||||||
|
|
||||||
|
# group all by the sessions title {session_id1: [...], session_id2: [...], ...}
|
||||||
|
grouped_cs_users = get_ordered_csus_by_course_session(course_session_ids)
|
||||||
|
grouped_cce = group_by_session_title(competence_certificate_elements)
|
||||||
|
grouped_ac = group_by_session_title(assignemnt_completions)
|
||||||
|
|
||||||
|
# create a sheet for each course session
|
||||||
|
for course_session, cs_users in grouped_cs_users.items():
|
||||||
|
logger.debug(
|
||||||
|
"export_assignment_completion",
|
||||||
|
data={
|
||||||
|
"course_session": course_session,
|
||||||
|
},
|
||||||
|
label="assignment_export",
|
||||||
|
)
|
||||||
|
_create_sheet(
|
||||||
|
wb,
|
||||||
|
course_session,
|
||||||
|
cs_users,
|
||||||
|
grouped_cce[course_session],
|
||||||
|
grouped_ac[course_session],
|
||||||
|
circle_ids,
|
||||||
|
)
|
||||||
|
|
||||||
|
if save_as_file:
|
||||||
|
wb.save(make_export_filename(name="competence_certificate_export"))
|
||||||
|
else:
|
||||||
|
output = BytesIO()
|
||||||
|
wb.save(output)
|
||||||
|
|
||||||
|
output.seek(0)
|
||||||
|
return output.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_sheet(
|
||||||
|
wb: Workbook,
|
||||||
|
title: str,
|
||||||
|
users: list[CourseSessionUser],
|
||||||
|
competence_certificate_element: list[CompetenceCertificateElement],
|
||||||
|
assignment_completions: list[AssignmentCompletion],
|
||||||
|
circle_ids: list[int],
|
||||||
|
):
|
||||||
|
sheet = wb.create_sheet(title=sanitize_sheet_name(title))
|
||||||
|
|
||||||
|
if len(users) == 0:
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
# headers
|
||||||
|
# common user headers, Circle <title> <learningcontenttitle> bestanden, Circle <title> <learningcontenttitle> Resultat, ...
|
||||||
|
col_idx = add_user_headers(sheet)
|
||||||
|
|
||||||
|
ordered_assignement_ids = (
|
||||||
|
[]
|
||||||
|
) # keep track of the order of the columns when adding the rows
|
||||||
|
for cse in competence_certificate_element:
|
||||||
|
circle = cse.learning_content.get_circle()
|
||||||
|
|
||||||
|
if circle_ids and circle.id not in circle_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
col_prefix = f'Circle "{circle.title}" {cse.learning_content.title} '
|
||||||
|
|
||||||
|
sheet.cell(
|
||||||
|
row=1,
|
||||||
|
column=col_idx,
|
||||||
|
value=f"{col_prefix} bestanden",
|
||||||
|
)
|
||||||
|
|
||||||
|
sheet.cell(
|
||||||
|
row=1,
|
||||||
|
column=col_idx + 1,
|
||||||
|
value=f"{col_prefix} Resultat %",
|
||||||
|
)
|
||||||
|
|
||||||
|
ordered_assignement_ids.append(cse.assignment.id)
|
||||||
|
|
||||||
|
col_idx += 2
|
||||||
|
|
||||||
|
# add rows with user results
|
||||||
|
_add_rows(sheet, users, ordered_assignement_ids, assignment_completions)
|
||||||
|
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def _add_rows(
|
||||||
|
sheet,
|
||||||
|
users: list[CourseSessionUser],
|
||||||
|
ordered_assignement_ids,
|
||||||
|
assignment_completions,
|
||||||
|
):
|
||||||
|
for row_idx, user in enumerate(users, start=2):
|
||||||
|
col_idx = add_user_export_data(sheet, user, row_idx)
|
||||||
|
|
||||||
|
for assignment_id in ordered_assignement_ids:
|
||||||
|
# get the completion for the user and the assignment
|
||||||
|
user_acs = [
|
||||||
|
ac
|
||||||
|
for ac in assignment_completions
|
||||||
|
if ac.assignment_id == assignment_id and ac.assignment_user == user.user
|
||||||
|
]
|
||||||
|
user_ac = user_acs[0] if user_acs else None
|
||||||
|
|
||||||
|
if user_ac:
|
||||||
|
status_text = (
|
||||||
|
"Bestanden" if user_ac.evaluation_passed else "Nicht bestanden"
|
||||||
|
)
|
||||||
|
sheet.cell(row=row_idx, column=col_idx, value=status_text)
|
||||||
|
try:
|
||||||
|
sheet.cell(
|
||||||
|
row=row_idx,
|
||||||
|
column=col_idx + 1,
|
||||||
|
value=round(
|
||||||
|
100
|
||||||
|
* user_ac.evaluation_points
|
||||||
|
/ user_ac.evaluation_max_points
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except (ZeroDivisionError, TypeError):
|
||||||
|
sheet.cell(row=row_idx, column=col_idx + 1, value="Keine Daten")
|
||||||
|
|
||||||
|
else:
|
||||||
|
sheet.cell(row=row_idx, column=col_idx, value="Keine Daten")
|
||||||
|
sheet.cell(row=row_idx, column=col_idx + 1, value="Keine Daten")
|
||||||
|
|
||||||
|
col_idx += 2
|
||||||
|
|
||||||
|
|
||||||
|
def _get_competence_certificate_elements(
|
||||||
|
course_session_ids: list[str],
|
||||||
|
) -> list[CompetenceCertificateElement]:
|
||||||
|
course_session_assignments = CourseSessionAssignment.objects.filter(
|
||||||
|
course_session__id__in=course_session_ids,
|
||||||
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||||
|
).order_by("course_session", "submission_deadline__start")
|
||||||
|
|
||||||
|
course_session_edoniqtests = CourseSessionEdoniqTest.objects.filter(
|
||||||
|
course_session__id__in=course_session_ids,
|
||||||
|
learning_content__content_assignment__competence_certificate__isnull=False,
|
||||||
|
).order_by("course_session", "deadline__start")
|
||||||
|
|
||||||
|
cse = [
|
||||||
|
CompetenceCertificateElement(
|
||||||
|
assignment=csa.learning_content.content_assignment,
|
||||||
|
date=csa.submission_deadline,
|
||||||
|
learning_content=csa.learning_content,
|
||||||
|
course_session=csa.course_session,
|
||||||
|
)
|
||||||
|
for csa in course_session_assignments
|
||||||
|
]
|
||||||
|
|
||||||
|
cse += [
|
||||||
|
CompetenceCertificateElement(
|
||||||
|
assignment=cset.learning_content.content_assignment,
|
||||||
|
date=cset.deadline,
|
||||||
|
learning_content=cset.learning_content,
|
||||||
|
course_session=cset.course_session,
|
||||||
|
)
|
||||||
|
for cset in course_session_edoniqtests
|
||||||
|
]
|
||||||
|
|
||||||
|
# order by course_session and submission_deadline
|
||||||
|
cse.sort(key=lambda x: (x.course_session.title, x.date.start))
|
||||||
|
|
||||||
|
return cse
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,13 @@ def export_attendance(
|
||||||
# remove the first sheet is just easier than keeping track of the active sheet
|
# remove the first sheet is just easier than keeping track of the active sheet
|
||||||
wb.remove(wb.active)
|
wb.remove(wb.active)
|
||||||
|
|
||||||
cs_users = CourseSessionUser.objects.filter(
|
|
||||||
course_session_id__in=course_session_ids, role=CourseSessionUser.Role.MEMBER
|
|
||||||
).order_by("course_session", "user__last_name", "user__first_name")
|
|
||||||
attendance_courses = CourseSessionAttendanceCourse.objects.filter(
|
attendance_courses = CourseSessionAttendanceCourse.objects.filter(
|
||||||
course_session_id__in=course_session_ids
|
course_session_id__in=course_session_ids
|
||||||
).order_by("course_session", "due_date")
|
).order_by("course_session", "due_date")
|
||||||
|
|
||||||
grouped_cs_users = {
|
grouped_cs_users = get_ordered_csus_by_course_session(course_session_ids)
|
||||||
key: list(group)
|
|
||||||
for key, group in groupby(
|
# create dict with course_session as key and list of attendance_courses as value. Easier to access in the loop
|
||||||
sorted(cs_users, key=lambda x: x.course_session.title),
|
|
||||||
key=lambda x: x.course_session.title,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
grouped_attendance_course = {
|
grouped_attendance_course = {
|
||||||
key: list(group)
|
key: list(group)
|
||||||
for key, group in groupby(
|
for key, group in groupby(
|
||||||
|
|
@ -42,6 +35,7 @@ def export_attendance(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# create a sheet for each course_session
|
||||||
for course_session, cs_users in grouped_cs_users.items():
|
for course_session, cs_users in grouped_cs_users.items():
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"export_attendance_for_course_session",
|
"export_attendance_for_course_session",
|
||||||
|
|
@ -81,14 +75,8 @@ def _create_sheet(
|
||||||
return sheet
|
return sheet
|
||||||
|
|
||||||
# headers
|
# headers
|
||||||
# firstname, lastname, email, lehrvertragsnummer, <attendance_course> <date>, status <attendance_course>, ..
|
# common user headers..., <attendance_course> <date>, status <attendance_course>, ..
|
||||||
# todo: translate headers
|
col_idx = add_user_headers(sheet)
|
||||||
sheet.cell(row=1, column=1, value="Vorname")
|
|
||||||
sheet.cell(row=1, column=2, value="Nachname")
|
|
||||||
sheet.cell(row=1, column=3, value="Email")
|
|
||||||
sheet.cell(row=1, column=4, value="Lehrvertragsnummer")
|
|
||||||
|
|
||||||
col_idx = 5
|
|
||||||
attendance_data = {}
|
attendance_data = {}
|
||||||
for course in attendance_courses:
|
for course in attendance_courses:
|
||||||
circle = course.get_circle()
|
circle = course.get_circle()
|
||||||
|
|
@ -105,6 +93,7 @@ def _create_sheet(
|
||||||
|
|
||||||
col_idx += 1
|
col_idx += 1
|
||||||
|
|
||||||
|
# add rows with user data
|
||||||
_add_rows(sheet, users, attendance_data)
|
_add_rows(sheet, users, attendance_data)
|
||||||
|
|
||||||
return sheet
|
return sheet
|
||||||
|
|
@ -112,16 +101,7 @@ def _create_sheet(
|
||||||
|
|
||||||
def _add_rows(sheet, users: list[CourseSessionUser], attendance_data):
|
def _add_rows(sheet, users: list[CourseSessionUser], attendance_data):
|
||||||
for row_idx, user in enumerate(users, start=2):
|
for row_idx, user in enumerate(users, start=2):
|
||||||
sheet.cell(row=row_idx, column=1, value=user.user.first_name)
|
col_idx = add_user_export_data(sheet, user, row_idx)
|
||||||
sheet.cell(row=row_idx, column=2, value=user.user.last_name)
|
|
||||||
sheet.cell(row=row_idx, column=3, value=user.user.email)
|
|
||||||
sheet.cell(
|
|
||||||
row=row_idx,
|
|
||||||
column=4,
|
|
||||||
value=user.user.additional_json_data.get("Lehrvertragsnummer", ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
col_idx = 5
|
|
||||||
for key, user_dict_map in attendance_data.items():
|
for key, user_dict_map in attendance_data.items():
|
||||||
user_dict = user_dict_map.get(str(user.user.id), {})
|
user_dict = user_dict_map.get(str(user.user.id), {})
|
||||||
status = user_dict.get("status", "") if user_dict else ""
|
status = user_dict.get("status", "") if user_dict else ""
|
||||||
|
|
@ -130,6 +110,48 @@ def _add_rows(sheet, users: list[CourseSessionUser], attendance_data):
|
||||||
col_idx += 1
|
col_idx += 1
|
||||||
|
|
||||||
|
|
||||||
|
def add_user_headers(sheet):
|
||||||
|
# todo: translate headers
|
||||||
|
sheet.cell(row=1, column=1, value="Vorname")
|
||||||
|
sheet.cell(row=1, column=2, value="Nachname")
|
||||||
|
sheet.cell(row=1, column=3, value="Email")
|
||||||
|
sheet.cell(row=1, column=4, value="Lehrvertragsnummer")
|
||||||
|
|
||||||
|
return 5 # return the next column index
|
||||||
|
|
||||||
|
|
||||||
|
def add_user_export_data(sheet, user: CourseSessionUser, row_idx: int) -> int:
|
||||||
|
sheet.cell(row=row_idx, column=1, value=user.user.first_name)
|
||||||
|
sheet.cell(row=row_idx, column=2, value=user.user.last_name)
|
||||||
|
sheet.cell(row=row_idx, column=3, value=user.user.email)
|
||||||
|
sheet.cell(
|
||||||
|
row=row_idx,
|
||||||
|
column=4,
|
||||||
|
value=user.user.additional_json_data.get("Lehrvertragsnummer", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
return 5 # return the next column index
|
||||||
|
|
||||||
|
|
||||||
|
def get_ordered_csus_by_course_session(course_session_ids: list[str]):
|
||||||
|
csus = CourseSessionUser.objects.filter(
|
||||||
|
course_session_id__in=course_session_ids, role=CourseSessionUser.Role.MEMBER
|
||||||
|
).order_by("course_session", "user__last_name", "user__first_name")
|
||||||
|
return group_by_session_title(
|
||||||
|
sorted(csus, key=lambda x: x.course_session.title),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def group_by_session_title(items):
|
||||||
|
return {
|
||||||
|
key: list(group)
|
||||||
|
for key, group in groupby(
|
||||||
|
sorted(items, key=lambda x: x.course_session.title),
|
||||||
|
key=lambda x: x.course_session.title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def make_export_filename(name: str = "attendance_export"):
|
def make_export_filename(name: str = "attendance_export"):
|
||||||
today_date = datetime.today().strftime("%Y-%m-%d")
|
today_date = datetime.today().strftime("%Y-%m-%d")
|
||||||
return f"{name}_{today_date}.xlsx"
|
return f"{name}_{today_date}.xlsx"
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,4 @@ from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CourseSessionGroup)
|
@admin.register(CourseSessionGroup)
|
||||||
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
class CourseSessionAssignmentAdmin(admin.ModelAdmin): ...
|
||||||
...
|
|
||||||
|
|
|
||||||
|
|
@ -433,9 +433,9 @@ def get_course_config(
|
||||||
is_mentor=is_mentor,
|
is_mentor=is_mentor,
|
||||||
widgets=get_widgets_for_course(role_key, is_uk, is_vv, is_mentor),
|
widgets=get_widgets_for_course(role_key, is_uk, is_vv, is_mentor),
|
||||||
has_preview=has_preview(role_key),
|
has_preview=has_preview(role_key),
|
||||||
session_to_continue_id=str(session_to_continue.id)
|
session_to_continue_id=(
|
||||||
if session_to_continue
|
str(session_to_continue.id) if session_to_continue else None
|
||||||
else None,
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -153,9 +153,9 @@ def fetch_course_session_all_users(courses: List[int], excluded_domains=None):
|
||||||
|
|
||||||
def generate_export_response(cs_users: List[User]) -> HttpResponse:
|
def generate_export_response(cs_users: List[User]) -> HttpResponse:
|
||||||
response = HttpResponse(content_type="text/csv; charset=utf-8")
|
response = HttpResponse(content_type="text/csv; charset=utf-8")
|
||||||
response[
|
response["Content-Disposition"] = (
|
||||||
"Content-Disposition"
|
f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
|
||||||
] = f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
|
)
|
||||||
|
|
||||||
response.write("\ufeff".encode("utf8")) # UTF-8 BOM
|
response.write("\ufeff".encode("utf8")) # UTF-8 BOM
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,9 +86,11 @@ def update_feedback_response(
|
||||||
initial_data = initial_data_for_feedback_page(learning_content_feedback_page)
|
initial_data = initial_data_for_feedback_page(learning_content_feedback_page)
|
||||||
|
|
||||||
merged_data = initial_data | {
|
merged_data = initial_data | {
|
||||||
key: updated_data[key]
|
key: (
|
||||||
if updated_data.get(key, "") != ""
|
updated_data[key]
|
||||||
else original_data.get(key)
|
if updated_data.get(key, "") != ""
|
||||||
|
else original_data.get(key)
|
||||||
|
)
|
||||||
for key in initial_data.keys()
|
for key in initial_data.keys()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,9 +251,9 @@ def _handle_feedback_export_action(course_seesions, file_name):
|
||||||
response = HttpResponse(
|
response = HttpResponse(
|
||||||
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
)
|
)
|
||||||
response[
|
response["Content-Disposition"] = (
|
||||||
"Content-Disposition"
|
f"attachment; filename={make_export_filename(file_name)}"
|
||||||
] = f"attachment; filename={make_export_filename(file_name)}"
|
)
|
||||||
response.write(excel_bytes)
|
response.write(excel_bytes)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,11 @@ def get_self_feedback_evaluation(
|
||||||
MentorAssignmentCompletion(
|
MentorAssignmentCompletion(
|
||||||
# feedback_submitted as seen from the perspective of the evaluation user (feedback provider)
|
# feedback_submitted as seen from the perspective of the evaluation user (feedback provider)
|
||||||
# means that the feedback has been evaluated by the feedback provider, hence the status is EVALUATED
|
# means that the feedback has been evaluated by the feedback provider, hence the status is EVALUATED
|
||||||
status=MentorCompletionStatus.EVALUATED
|
status=(
|
||||||
if f.feedback_submitted
|
MentorCompletionStatus.EVALUATED
|
||||||
else MentorCompletionStatus.SUBMITTED,
|
if f.feedback_submitted
|
||||||
|
else MentorCompletionStatus.SUBMITTED
|
||||||
|
),
|
||||||
user_id=f.feedback_requester_user.id,
|
user_id=f.feedback_requester_user.id,
|
||||||
last_name=f.feedback_requester_user.last_name,
|
last_name=f.feedback_requester_user.last_name,
|
||||||
url=f"/course/{course.slug}/cockpit/mentor/self-evaluation-feedback/{f.learning_unit.id}",
|
url=f"/course/{course.slug}/cockpit/mentor/self-evaluation-feedback/{f.learning_unit.id}",
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,9 @@ class TestNotificationService(TestCase):
|
||||||
self.assertFalse(notification.emailed)
|
self.assertFalse(notification.emailed)
|
||||||
|
|
||||||
def test_send_notification_with_email(self):
|
def test_send_notification_with_email(self):
|
||||||
self.recipient.additional_json_data[
|
self.recipient.additional_json_data["email_notification_categories"] = (
|
||||||
"email_notification_categories"
|
json.dumps(["USER_INTERACTION"])
|
||||||
] = json.dumps(["USER_INTERACTION"])
|
)
|
||||||
self.recipient.save()
|
self.recipient.save()
|
||||||
|
|
||||||
verb = "Anne hat deinen Auftrag bewertet"
|
verb = "Anne hat deinen Auftrag bewertet"
|
||||||
|
|
@ -146,9 +146,9 @@ class TestNotificationService(TestCase):
|
||||||
self.assertFalse(notification.emailed)
|
self.assertFalse(notification.emailed)
|
||||||
|
|
||||||
# when the email was not sent, yet it will still send it afterwards...
|
# when the email was not sent, yet it will still send it afterwards...
|
||||||
self.recipient.additional_json_data[
|
self.recipient.additional_json_data["email_notification_categories"] = (
|
||||||
"email_notification_categories"
|
json.dumps(["USER_INTERACTION"])
|
||||||
] = json.dumps(["USER_INTERACTION"])
|
)
|
||||||
self.recipient.save()
|
self.recipient.save()
|
||||||
|
|
||||||
result = self.notification_service._send_notification(
|
result = self.notification_service._send_notification(
|
||||||
|
|
@ -188,9 +188,9 @@ class TestNotificationService(TestCase):
|
||||||
self.assertFalse(self._has_sent_emails())
|
self.assertFalse(self._has_sent_emails())
|
||||||
|
|
||||||
# Assert mail is sent if corresponding email notification type is enabled
|
# Assert mail is sent if corresponding email notification type is enabled
|
||||||
self.recipient.additional_json_data[
|
self.recipient.additional_json_data["email_notification_categories"] = (
|
||||||
"email_notification_categories"
|
json.dumps(["USER_INTERACTION"])
|
||||||
] = json.dumps(["USER_INTERACTION"])
|
)
|
||||||
self.recipient.save()
|
self.recipient.save()
|
||||||
self.notification_service._send_notification(
|
self.notification_service._send_notification(
|
||||||
sender=self.sender,
|
sender=self.sender,
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer):
|
||||||
return obj.learning_unit.get_circle().title
|
return obj.learning_unit.get_circle().title
|
||||||
|
|
||||||
def get_criteria(self, obj):
|
def get_criteria(self, obj):
|
||||||
performance_criteria: List[
|
performance_criteria: List[PerformanceCriteria] = (
|
||||||
PerformanceCriteria
|
obj.learning_unit.performancecriteria_set.all()
|
||||||
] = obj.learning_unit.performancecriteria_set.all()
|
)
|
||||||
|
|
||||||
criteria = []
|
criteria = []
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,15 @@ class AbacusInvoiceCreator(InvoiceCreator):
|
||||||
)
|
)
|
||||||
|
|
||||||
SubElement(sales_order_header_fields, "CustomerNumber").text = customer_number
|
SubElement(sales_order_header_fields, "CustomerNumber").text = customer_number
|
||||||
SubElement(
|
SubElement(sales_order_header_fields, "PurchaseOrderDate").text = (
|
||||||
sales_order_header_fields, "PurchaseOrderDate"
|
order_date.isoformat()
|
||||||
).text = order_date.isoformat()
|
)
|
||||||
SubElement(
|
SubElement(sales_order_header_fields, "DeliveryDate").text = (
|
||||||
sales_order_header_fields, "DeliveryDate"
|
order_date.isoformat()
|
||||||
).text = order_date.isoformat()
|
)
|
||||||
SubElement(
|
SubElement(sales_order_header_fields, "ReferencePurchaseOrder").text = (
|
||||||
sales_order_header_fields, "ReferencePurchaseOrder"
|
reference_purchase_order
|
||||||
).text = reference_purchase_order
|
)
|
||||||
SubElement(sales_order_header_fields, "UnicId").text = unic_id
|
SubElement(sales_order_header_fields, "UnicId").text = unic_id
|
||||||
|
|
||||||
for index, item in enumerate(items, start=1):
|
for index, item in enumerate(items, start=1):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue