wip: Add views

This commit is contained in:
Christian Cueni 2024-05-28 14:14:17 +02:00
parent b16016b34c
commit 6244e02489
12 changed files with 93 additions and 33 deletions

View File

@ -4,6 +4,7 @@ from django.conf.urls.static import static
from django.contrib import admin
from django.contrib.auth.decorators import user_passes_test
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.http import HttpResponse
from django.urls import include, path, re_path, register_converter
from django.urls.converters import IntConverter
from django.views import defaults as default_views
@ -40,6 +41,7 @@ from vbv_lernwelt.course.views import (
)
from vbv_lernwelt.course_session.views import get_course_session_documents
from vbv_lernwelt.dashboard.views import (
export_attendance_as_xsl,
get_dashboard_config,
get_dashboard_due_dates,
get_dashboard_persons,
@ -130,6 +132,7 @@ urlpatterns = [
path(r"api/dashboard/course/<str:course_id>/mentees/", get_mentee_count, name="get_mentee_count"),
path(r"api/dashboard/course/<str:course_id>/open_tasks/", get_mentor_open_tasks_count,
name="get_mentor_open_tasks_count"),
path(r"api/dashboard/export/attendance", export_attendance_as_xsl, name="export_attendance_as_xsl"),
# course
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),

View File

@ -15,4 +15,4 @@ logger = structlog.get_logger(__name__)
)
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)
export_competence_certificates([course_session_id], save_as_file=save_as_file)

View File

@ -306,7 +306,9 @@ def _remove_unknown_entries(assignment, completion_data):
def export_competence_certificates(
course_session_ids: list[str], save_as_file: bool, circle_ids: list[int] = None
course_session_ids: list[str],
circle_ids: list[int] = None,
save_as_file: bool = False,
):
if len(course_session_ids) == 0:
return

View File

@ -15,4 +15,4 @@ logger = structlog.get_logger(__name__)
)
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_attendance([course_session_id], save_as_file)
export_attendance([course_session_id], save_as_file=save_as_file)

View File

@ -13,7 +13,9 @@ logger = structlog.get_logger(__name__)
def export_attendance(
course_session_ids: list[str], save_as_file: bool, circle_ids: list[int] = None
course_session_ids: list[str],
save_as_file: bool = False,
circle_ids: list[int] = None,
):
wb = Workbook()

View File

@ -4,4 +4,5 @@ from vbv_lernwelt.course_session_group.models import CourseSessionGroup
@admin.register(CourseSessionGroup)
class CourseSessionAssignmentAdmin(admin.ModelAdmin): ...
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
...

View File

@ -3,6 +3,8 @@ from datetime import date
from enum import Enum
from typing import List, Set
from django.http import HttpResponse
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
@ -11,6 +13,7 @@ from vbv_lernwelt.assignment.models import (
AssignmentCompletion,
AssignmentCompletionStatus,
)
from vbv_lernwelt.assignment.services import export_competence_certificates
from vbv_lernwelt.competence.services import (
query_competence_course_session_assignments,
query_competence_course_session_edoniq_tests,
@ -22,6 +25,10 @@ from vbv_lernwelt.course.models import (
CourseSessionUser,
)
from vbv_lernwelt.course.views import logger
from vbv_lernwelt.course_session.services.export import (
export_attendance,
make_export_filename,
)
from vbv_lernwelt.course_session_group.models import CourseSessionGroup
from vbv_lernwelt.duedate.models import DueDate
from vbv_lernwelt.duedate.serializers import DueDateSerializer
@ -494,7 +501,7 @@ def get_mentor_open_tasks_count(request, course_id: str):
raise e
except Exception as e:
logger.error(e, exc_info=True)
return Response({"error": str(e)}, status=404)
return Response({"error": str(e)}, status=status.HTTP_404_NOT_FOUND)
def _get_mentor_open_tasks_count(course_id: str, mentor: User) -> int:
@ -526,3 +533,48 @@ def _get_mentor_open_tasks_count(course_id: str, mentor: User) -> int:
)
return open_assigment_count + open_feedback_count
@api_view(["POST"])
def export_attendance_as_xsl(request):
return _generate_xls_export(request, export_attendance)
@api_view(["POST"])
def export_competence_certificate_as_xsl(request):
return _generate_xls_export(request, export_competence_certificates)
def _generate_xls_export(request, export_fn) -> HttpResponse:
requested_course_session_ids = request.data.get("courseSessionIds", [])
circle_ids = request.data.get("circleIds", None)
if not requested_course_session_ids:
return Response({"error": "no_cs_ids"}, status=status.HTTP_400_BAD_REQUEST)
course_session_ids = _get_allowed_course_session_ids_for_user(
request.user, requested_course_session_ids
) # noqa
data = export_fn(course_session_ids, circle_ids=circle_ids)
response = HttpResponse(
data,
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
response["Content-Disposition"] = f'attachment; filename="{make_export_filename()}"'
return response
def _get_allowed_course_session_ids_for_user(
user: User, requested_cs_ids: List[str]
) -> List[str]:
ALLOWED_ROLES = ["TRAINER", "SUPERVISOR"]
# 1. get course sessions for user with allowed roles
# 2. get overlapping course sessions with given course_session_ids
# Note: We don't care about the circle_ids as it's ok-ish that trainers could export other data
all_cs_ids_for_user = [
csr._original.id
for csr in get_course_sessions_with_roles_for_user(user)
if any(allowed_role in ALLOWED_ROLES for role in csr.roles)
] # noqa
return list(set(requested_cs_ids) & set(all_cs_ids_for_user))

View File

@ -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:
response = HttpResponse(content_type="text/csv; charset=utf-8")
response["Content-Disposition"] = (
f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
)
response[
"Content-Disposition"
] = f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
response.write("\ufeff".encode("utf8")) # UTF-8 BOM

View File

@ -251,9 +251,9 @@ def _handle_feedback_export_action(course_seesions, file_name):
response = HttpResponse(
content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
response["Content-Disposition"] = (
f"attachment; filename={make_export_filename(file_name)}"
)
response[
"Content-Disposition"
] = f"attachment; filename={make_export_filename(file_name)}"
response.write(excel_bytes)
return response

View File

@ -65,9 +65,9 @@ class TestNotificationService(TestCase):
self.assertFalse(notification.emailed)
def test_send_notification_with_email(self):
self.recipient.additional_json_data["email_notification_categories"] = (
json.dumps(["USER_INTERACTION"])
)
self.recipient.additional_json_data[
"email_notification_categories"
] = json.dumps(["USER_INTERACTION"])
self.recipient.save()
verb = "Anne hat deinen Auftrag bewertet"
@ -146,9 +146,9 @@ class TestNotificationService(TestCase):
self.assertFalse(notification.emailed)
# when the email was not sent, yet it will still send it afterwards...
self.recipient.additional_json_data["email_notification_categories"] = (
json.dumps(["USER_INTERACTION"])
)
self.recipient.additional_json_data[
"email_notification_categories"
] = json.dumps(["USER_INTERACTION"])
self.recipient.save()
result = self.notification_service._send_notification(
@ -188,9 +188,9 @@ class TestNotificationService(TestCase):
self.assertFalse(self._has_sent_emails())
# Assert mail is sent if corresponding email notification type is enabled
self.recipient.additional_json_data["email_notification_categories"] = (
json.dumps(["USER_INTERACTION"])
)
self.recipient.additional_json_data[
"email_notification_categories"
] = json.dumps(["USER_INTERACTION"])
self.recipient.save()
self.notification_service._send_notification(
sender=self.sender,

View File

@ -39,9 +39,9 @@ class SelfEvaluationFeedbackSerializer(serializers.ModelSerializer):
return obj.learning_unit.get_circle().title
def get_criteria(self, obj):
performance_criteria: List[PerformanceCriteria] = (
obj.learning_unit.performancecriteria_set.all()
)
performance_criteria: List[
PerformanceCriteria
] = obj.learning_unit.performancecriteria_set.all()
criteria = []

View File

@ -67,15 +67,15 @@ class AbacusInvoiceCreator(InvoiceCreator):
)
SubElement(sales_order_header_fields, "CustomerNumber").text = customer_number
SubElement(sales_order_header_fields, "PurchaseOrderDate").text = (
order_date.isoformat()
)
SubElement(sales_order_header_fields, "DeliveryDate").text = (
order_date.isoformat()
)
SubElement(sales_order_header_fields, "ReferencePurchaseOrder").text = (
reference_purchase_order
)
SubElement(
sales_order_header_fields, "PurchaseOrderDate"
).text = order_date.isoformat()
SubElement(
sales_order_header_fields, "DeliveryDate"
).text = order_date.isoformat()
SubElement(
sales_order_header_fields, "ReferencePurchaseOrder"
).text = reference_purchase_order
SubElement(sales_order_header_fields, "UnicId").text = unic_id
for index, item in enumerate(items, start=1):