wip: Add views
This commit is contained in:
parent
b16016b34c
commit
6244e02489
|
|
@ -4,6 +4,7 @@ from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.decorators import user_passes_test
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
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 import include, path, re_path, register_converter
|
||||||
from django.urls.converters import IntConverter
|
from django.urls.converters import IntConverter
|
||||||
from django.views import defaults as default_views
|
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.course_session.views import get_course_session_documents
|
||||||
from vbv_lernwelt.dashboard.views import (
|
from vbv_lernwelt.dashboard.views import (
|
||||||
|
export_attendance_as_xsl,
|
||||||
get_dashboard_config,
|
get_dashboard_config,
|
||||||
get_dashboard_due_dates,
|
get_dashboard_due_dates,
|
||||||
get_dashboard_persons,
|
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>/mentees/", get_mentee_count, name="get_mentee_count"),
|
||||||
path(r"api/dashboard/course/<str:course_id>/open_tasks/", get_mentor_open_tasks_count,
|
path(r"api/dashboard/course/<str:course_id>/open_tasks/", get_mentor_open_tasks_count,
|
||||||
name="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
|
# course
|
||||||
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
path(r"api/course/sessions/", get_course_sessions, name="get_course_sessions"),
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@ logger = structlog.get_logger(__name__)
|
||||||
)
|
)
|
||||||
def command(course_session_id, 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
|
# 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)
|
||||||
|
|
|
||||||
|
|
@ -306,7 +306,9 @@ def _remove_unknown_entries(assignment, completion_data):
|
||||||
|
|
||||||
|
|
||||||
def export_competence_certificates(
|
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:
|
if len(course_session_ids) == 0:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@ logger = structlog.get_logger(__name__)
|
||||||
)
|
)
|
||||||
def command(course_session_id, 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
|
# 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)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def export_attendance(
|
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()
|
wb = Workbook()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ from vbv_lernwelt.course_session_group.models import CourseSessionGroup
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CourseSessionGroup)
|
@admin.register(CourseSessionGroup)
|
||||||
class CourseSessionAssignmentAdmin(admin.ModelAdmin): ...
|
class CourseSessionAssignmentAdmin(admin.ModelAdmin):
|
||||||
|
...
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from datetime import date
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Set
|
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.decorators import api_view
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
@ -11,6 +13,7 @@ from vbv_lernwelt.assignment.models import (
|
||||||
AssignmentCompletion,
|
AssignmentCompletion,
|
||||||
AssignmentCompletionStatus,
|
AssignmentCompletionStatus,
|
||||||
)
|
)
|
||||||
|
from vbv_lernwelt.assignment.services import export_competence_certificates
|
||||||
from vbv_lernwelt.competence.services import (
|
from vbv_lernwelt.competence.services import (
|
||||||
query_competence_course_session_assignments,
|
query_competence_course_session_assignments,
|
||||||
query_competence_course_session_edoniq_tests,
|
query_competence_course_session_edoniq_tests,
|
||||||
|
|
@ -22,6 +25,10 @@ from vbv_lernwelt.course.models import (
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
)
|
)
|
||||||
from vbv_lernwelt.course.views import logger
|
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.course_session_group.models import CourseSessionGroup
|
||||||
from vbv_lernwelt.duedate.models import DueDate
|
from vbv_lernwelt.duedate.models import DueDate
|
||||||
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
from vbv_lernwelt.duedate.serializers import DueDateSerializer
|
||||||
|
|
@ -494,7 +501,7 @@ def get_mentor_open_tasks_count(request, course_id: str):
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e, exc_info=True)
|
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:
|
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
|
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))
|
||||||
|
|
|
||||||
|
|
@ -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["Content-Disposition"] = (
|
response[
|
||||||
f"attachment; filename=edoniq_user_export_{date.today().strftime('%Y%m%d')}.csv"
|
"Content-Disposition"
|
||||||
)
|
] = 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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -251,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["Content-Disposition"] = (
|
response[
|
||||||
f"attachment; filename={make_export_filename(file_name)}"
|
"Content-Disposition"
|
||||||
)
|
] = f"attachment; filename={make_export_filename(file_name)}"
|
||||||
response.write(excel_bytes)
|
response.write(excel_bytes)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -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["email_notification_categories"] = (
|
self.recipient.additional_json_data[
|
||||||
json.dumps(["USER_INTERACTION"])
|
"email_notification_categories"
|
||||||
)
|
] = 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["email_notification_categories"] = (
|
self.recipient.additional_json_data[
|
||||||
json.dumps(["USER_INTERACTION"])
|
"email_notification_categories"
|
||||||
)
|
] = 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["email_notification_categories"] = (
|
self.recipient.additional_json_data[
|
||||||
json.dumps(["USER_INTERACTION"])
|
"email_notification_categories"
|
||||||
)
|
] = 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[PerformanceCriteria] = (
|
performance_criteria: List[
|
||||||
obj.learning_unit.performancecriteria_set.all()
|
PerformanceCriteria
|
||||||
)
|
] = 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(sales_order_header_fields, "PurchaseOrderDate").text = (
|
SubElement(
|
||||||
order_date.isoformat()
|
sales_order_header_fields, "PurchaseOrderDate"
|
||||||
)
|
).text = order_date.isoformat()
|
||||||
SubElement(sales_order_header_fields, "DeliveryDate").text = (
|
SubElement(
|
||||||
order_date.isoformat()
|
sales_order_header_fields, "DeliveryDate"
|
||||||
)
|
).text = order_date.isoformat()
|
||||||
SubElement(sales_order_header_fields, "ReferencePurchaseOrder").text = (
|
SubElement(
|
||||||
reference_purchase_order
|
sales_order_header_fields, "ReferencePurchaseOrder"
|
||||||
)
|
).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