diff --git a/api/urls.py b/api/urls.py index fc71b98e..a2716880 100644 --- a/api/urls.py +++ b/api/urls.py @@ -16,6 +16,7 @@ path("classes//add_students", views.add_student_to_class), path("subject/", views.subject_list), path("subjects/all", views.subjects_all), + path("teachers/all", views.teachers_all), path("reevaluate_task/", views.reevaluate_task), path("search", views.search), path("transfer_students", views.transfer_students), diff --git a/api/views.py b/api/views.py index 100dae70..80d77d7b 100644 --- a/api/views.py +++ b/api/views.py @@ -3,7 +3,7 @@ import django.http from django.shortcuts import get_object_or_404, resolve_url from django.http import HttpRequest, HttpResponseBadRequest -from django.views.decorators.http import require_POST +from django.views.decorators.http import require_GET, require_POST from django.contrib.auth.models import User from django.urls import reverse from common.models import ( @@ -284,6 +284,19 @@ def subjects_all(request) -> JsonResponse: return JsonResponse(resp) +@user_passes_test(is_teacher) +@require_GET +def teachers_all(request) -> JsonResponse: + teachers = User.objects.filter(groups__name="teachers") + items = tuple( + {"username": t.username, "full_name": t.get_full_name(), "last_name": t.last_name} + for t in teachers + ) + + resp = {"teachers": items} + return JsonResponse(resp) + + @login_required def info(request): res = {} @@ -745,8 +758,16 @@ def import_activities(request): post = json.loads(request.body.decode("utf-8")) semester_id = post["semester_id"] - subject = post["subject"] + subject_abbr = post["subject_abbr"] activities_id = post["activities"] + activities_to_teacher = post[ + "activities_to_teacher" + ] # activities_to_teacher: when INBUS doesn't provide one, UI user selects one + + activities_to_teacher = { + int(activity_id): teacher_username + for activity_id, teacher_username in activities_to_teacher.items() + } activities = [inbus.concrete_activity(activity_id) for activity_id in activities_id] activities = [ @@ -755,10 +776,16 @@ def import_activities(request): semester = Semester.objects.get(pk=semester_id) try: - res["users"] = list(common.bulk_import.run(activities, subject, semester, request.user)) + res["users"] = list( + common.bulk_import.run( + activities, subject_abbr, semester, request.user, activities_to_teacher + ) + ) res["count"] = len(res["users"]) except (ImportException, UnicodeDecodeError) as e: - res["error"] = "".join(traceback.TracebackException.from_exception(e).format()) + # msg = traceback.TracebackException.from_exception(e).format() + msg = e.args[0] + res["error"] = msg except BaseException: res["error"] = traceback.format_exc() diff --git a/common/bulk_import.py b/common/bulk_import.py index 9e5b37b5..dd470e64 100644 --- a/common/bulk_import.py +++ b/common/bulk_import.py @@ -8,7 +8,7 @@ from typing import List, Dict, Generator import traceback -from .inbus.dto import ConcreteActivity +from .inbus.dto import ConcreteActivity, ConcreteActivityId class ImportException(Exception): @@ -26,15 +26,17 @@ class ImportResult: def run( concrete_activities: List[ConcreteActivity], - subj: Dict[str, str], + subject_abbr: str, semester: Semester, user: User, + activities_to_teacher: Dict[int, str], ) -> Generator[ImportResult, None, None]: """ - `subj`: subject from selected subject in UI as dictionary with k:abbr, v: name + `subject_addr`: subject abbreviation from selected subject in UI + `user`: importing user (the that uses import UI) + `activities_to_teacher`: dictionary of activities and manually assigned teachers (username) in the UI """ - subject_abbr = subj["abbr"] try: subject = Subject.objects.get(abbr=subject_abbr) except Subject.DoesNotExist: @@ -75,8 +77,13 @@ def run( f"Cannot create user {ca.teacherLogins.upper()}.\n\nTraceback\n\n{traceback.format_exc()}" ) else: - # TODO: We assign all activities without teacher to one special user :-) - teacher = User.objects.get(username="GAU01") + # We assign all activities without teacher in INBUS to the one selected by importing user + try: + teacher_username = activities_to_teacher[ca.concreteActivityId] + teacher = User.objects.get(username=teacher_username) + except KeyError: + msg = f"There's no assigned teacher to activity {ca.code()}. Please, make sure you selected one." + raise ImportException(msg) if not is_teacher(teacher): teachers_group = Group.objects.get_by_natural_key("teachers") @@ -87,7 +94,9 @@ def run( class_in_db[c].save() # Students - students_in_class = inbus.students_in_concrete_activity(ca.concreteActivityId) + students_in_class = inbus.students_in_concrete_activity( + ConcreteActivityId(ca.concreteActivityId) + ) for student in students_in_class: login = student.login.upper() diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index f2bba4b0..6b4d51e6 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -3,14 +3,12 @@ import EditTask from './EditTask.svelte'; import ClassList from './ClassList.svelte'; import Router from 'svelte-spa-router'; import StudentTransfer from './StudentTransfer.svelte'; -import ImportFromInbus from './ImportFromInbus.svelte'; import { user } from './global.js'; const routes = { '/task/edit/:id': EditTask, '/task/add/:subject': EditTask, '/student_transfer': StudentTransfer, - '/import': ImportFromInbus, '/': ClassList }; diff --git a/frontend/src/ClassList.svelte b/frontend/src/ClassList.svelte index f11ed12a..94d30edb 100644 --- a/frontend/src/ClassList.svelte +++ b/frontend/src/ClassList.svelte @@ -63,7 +63,7 @@ $: { teacher={filter.teacher} clazz={filter.class} /> - + diff --git a/frontend/src/ImportFromInbus.svelte b/frontend/src/ImportFromInbus.svelte deleted file mode 100644 index 077efa10..00000000 --- a/frontend/src/ImportFromInbus.svelte +++ /dev/null @@ -1,209 +0,0 @@ - - -{#if semesters} - -{/if} - -{#if subjects_kelvin} - -{/if} - -{#if subjects_inbus_filtered} - -{/if} - -{#if subject_inbus_schedule} - - - {#each subject_inbus_schedule as ca} - - - - - - - - - - - - - - {/each} - -
- - - {ca.educationTypeAbbrev}/{ca.order}, {ca.subjectVersionCompleteCode} - - {ca.teacherFullNames} - - {ca.weekDayAbbrev} - - {ca.beginTime} - - {ca.endTime} -
-{/if} - - - -{#if result} - {#if result.error} - - {:else} - - - {#each result.users as item} - - - - - - - {/each} - -
{item.login}{item.firstname}{item.lastname}{item.created}
- {/if} -{/if} diff --git a/frontend/src/Teacher/InbusImport.vue b/frontend/src/Teacher/InbusImport.vue new file mode 100644 index 00000000..9bff1e82 --- /dev/null +++ b/frontend/src/Teacher/InbusImport.vue @@ -0,0 +1,391 @@ + + + + + diff --git a/frontend/src/Teacher/inbusdto.ts b/frontend/src/Teacher/inbusdto.ts new file mode 100644 index 00000000..ac23d97b --- /dev/null +++ b/frontend/src/Teacher/inbusdto.ts @@ -0,0 +1,27 @@ +export interface ConcreteActivity { + /** + Concrete activity in schedule. + */ + concreteActivityId: number; + order: number; + subjectVersionId: number; + subjectVersionCompleteCode: string; + educationTypeAbbrev: string; + beginTime: string; + endTime: string; + weekDayAbbrev: string; + teacherFullNames?: string; +} + +interface InbusSubject { + subjectId: number; + code: string; + abbrev: string; + title: string; +} + +export interface InbusSubjectVersion { + subjectVersionId: number; + subject: InbusSubject; + subjectVersionCompleteCode: string; +} diff --git a/frontend/src/main.js b/frontend/src/main.js index 3b233df2..77144ba0 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -146,6 +146,7 @@ window.addEventListener('DOMContentLoaded', focusTab); import { defineCustomElement, h } from 'vue'; import SuspensionWrapper from './components/SuspensionWrapper.vue'; import AllTasks from './Teacher/AllTasks.vue'; +import InbusImport from './Teacher/InbusImport.vue'; /** * Register new Vue component as a custom element. @@ -182,3 +183,4 @@ const registerSuspendedVueComponent = (name, component, configureApp = undefined }; registerSuspendedVueComponent('tasks-all', AllTasks); +registerSuspendedVueComponent('inbus-import', InbusImport); diff --git a/templates/web/inbusimport.html b/templates/web/inbusimport.html new file mode 100644 index 00000000..def17b63 --- /dev/null +++ b/templates/web/inbusimport.html @@ -0,0 +1,5 @@ +{% extends 'web/layout.html' %} + +{% block fullcontent %} + +{% endblock %} diff --git a/web/urls.py b/web/urls.py index c44a9f40..9fadbe8b 100644 --- a/web/urls.py +++ b/web/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path("", common_view.index, name="index"), + path("import/inbus", common_view.import_inbus, name="import_inbus"), path("student-view", student_view.student_index, name="student_index"), path( "find-task///", diff --git a/web/views/common.py b/web/views/common.py index b55c6237..4569c3a7 100644 --- a/web/views/common.py +++ b/web/views/common.py @@ -1,4 +1,4 @@ -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, user_passes_test from django.shortcuts import render from django.utils.crypto import get_random_string from django.conf import settings @@ -17,6 +17,11 @@ def index(request): return student_index(request) +@user_passes_test(is_teacher) +def import_inbus(request): + return render(request, "web/inbusimport.html", {}) + + @login_required() def api_token(request): data = {