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/<int:class_id>/add_students", views.add_student_to_class),
     path("subject/<subject_abbr>", views.subject_list),
     path("subjects/all", views.subjects_all),
+    path("teachers/all", views.teachers_all),
     path("reevaluate_task/<int:task_id>", 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
 };
 </script>
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} />
 
-    <a class="btn btn-sm p-1" href="/#/import" title="Bulk import students from EDISON">
+    <a class="btn btn-sm p-1" href="/import/inbus" title="Bulk import students from EDISON">
       <span class="iconify" data-icon="mdi:calendar-import"></span>
     </a>
 
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 @@
-<script>
-import { fetch } from './api.js';
-
-let subject_inbus_selected = null;
-let subject_kelvin_selected = null;
-
-let semester = null;
-let busy = false;
-
-let semesters = null;
-let subjects_inbus = null;
-let subjects_inbus_filtered = null;
-let subjects_kelvin = null;
-let subject_inbus_schedule = null;
-
-let classes_to_import = [];
-let result = null;
-
-$: canImport = classes_to_import.length && !busy;
-
-function svcc2num(svcc) {
-  let [dept_code, version] = svcc.split('/');
-  let [dept, code] = dept_code.split('-');
-
-  let svcc_str = dept + code + version;
-  let svcc_num = Number(svcc_str);
-
-  return svcc_num;
-}
-
-async function loadInbusAndKelvinSubjects() {
-  const res1 = await fetch('/api/inbus/subject_versions');
-  const res2 = await fetch('/api/subjects/all');
-
-  subjects_inbus = await res1.json();
-  subjects_kelvin = await res2.json();
-
-  subjects_kelvin = subjects_kelvin.subjects;
-  subjects_kelvin.sort((a, b) => {
-    const name_a = a.name.toUpperCase();
-    const name_b = b.name.toUpperCase();
-    if (name_a < name_b) {
-      return -1;
-    }
-    if (name_a > name_b) {
-      return 1;
-    }
-    return 0;
-  });
-
-  const subject_kelvin_abbrs = subjects_kelvin.map((s) => s.abbr);
-
-  subjects_inbus_filtered = subjects_inbus.filter((subject_inbus) =>
-    subject_kelvin_abbrs.includes(subject_inbus.subject.abbrev)
-  );
-  subjects_inbus_filtered = subjects_inbus_filtered.sort(
-    (a, b) => svcc2num(a.subjectVersionCompleteCode) - svcc2num(b.subjectVersionCompleteCode)
-  );
-}
-
-function parseSemesters(semesters_data) {
-  semesters = semesters_data.map((sm) => ({
-    pk: sm.pk,
-    year: sm.year,
-    winter: sm.winter,
-    display: new String(sm.year) + (sm.winter ? 'W' : 'S')
-  }));
-}
-
-async function loadSemesters() {
-  const res = await fetch('/api/semesters');
-  const semesters_data = await res.json();
-  parseSemesters(semesters_data['semesters']);
-}
-
-async function loadScheduleForSubjectVersionId() {
-  let res = await fetch(
-    `/api/inbus/schedule/subject/version/${subject_inbus_selected.subjectVersionId}`
-  );
-  subject_inbus_schedule = await res.json();
-}
-
-$: if (subject_inbus_selected) {
-  loadScheduleForSubjectVersionId();
-}
-
-async function import_activities() {
-  busy = true;
-  const req = {
-    semester_id: semester,
-    subject: subject_kelvin_selected,
-    activities: classes_to_import
-  };
-
-  const res = await fetch('/api/import/activities', {
-    method: 'POST',
-    headers: {
-      Accept: 'application/json',
-      'Content-Type': 'application/json'
-    },
-    body: JSON.stringify(req)
-  });
-
-  result = await res.json();
-  busy = false;
-}
-
-loadSemesters();
-loadInbusAndKelvinSubjects();
-</script>
-
-{#if semesters}
-  <select bind:value={semester}>
-    {#each semesters as item}
-      <option value={item.pk}>{item.display}</option>
-    {/each}
-  </select>
-{/if}
-
-{#if subjects_kelvin}
-  <select bind:value={subject_kelvin_selected}>
-    {#each subjects_kelvin as item}
-      <option value={item}>{item.abbr} - {item.name}</option>
-    {/each}
-  </select>
-{/if}
-
-{#if subjects_inbus_filtered}
-  <select bind:value={subject_inbus_selected}>
-    {#each subjects_inbus_filtered as item}
-      <option value={item}
-        >{item.subjectVersionCompleteCode} - {item.subject.abbrev} - {item.subject.title}</option>
-    {/each}
-  </select>
-{/if}
-
-{#if subject_inbus_schedule}
-  <table class="table table-hover table-stripped table-sm">
-    <tbody>
-      {#each subject_inbus_schedule as ca}
-        <!-- `ca` stands for concrete_activity -->
-        <tr>
-          <td>
-            <label>
-              <input
-                type="checkbox"
-                bind:group={classes_to_import}
-                name="classesToImport"
-                value={ca.concreteActivityId} />
-              {ca.educationTypeAbbrev}
-            </label>
-          </td>
-
-          <td>
-            {ca.educationTypeAbbrev}/{ca.order}, {ca.subjectVersionCompleteCode}
-          </td>
-          <td>
-            {ca.teacherFullNames}
-          </td>
-
-          <td>
-            {ca.weekDayAbbrev}
-          </td>
-
-          <td>
-            {ca.beginTime}
-          </td>
-
-          <td>
-            {ca.endTime}
-          </td>
-        </tr>
-      {/each}
-    </tbody>
-  </table>
-{/if}
-
-<button
-  class="btn btn-success"
-  on:click={import_activities}
-  disabled={!canImport}
-  class:btn-danger={!canImport}>
-  {#if busy}
-    Importing...
-  {:else}
-    Import
-  {/if}
-</button>
-
-{#if result}
-  {#if result.error}
-    <div class="alert alert-danger" role="alert">
-      {result.error}
-    </div>
-  {:else}
-    <table class="table table-sm table-hover table-striped">
-      <tbody>
-        {#each result.users as item}
-          <tr>
-            <td>{item.login}</td>
-            <td>{item.firstname}</td>
-            <td>{item.lastname}</td>
-            <td>{item.created}</td>
-          </tr>
-        {/each}
-      </tbody>
-    </table>
-  {/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 @@
+<script setup lang="ts">
+/**
+ * This component provides import of classes of students from Edison
+ * into Kelvin using the INBUS API.
+ *
+ * It is only available to teachers, and it is accessible from the main page
+ * next to class selection drop down menu.
+ */
+import { computed, ref } from 'vue';
+
+import { csrfToken } from '../api.js';
+import { ConcreteActivity, InbusSubjectVersion } from './inbusdto';
+
+interface KelvinSubject {
+  name: string;
+  abbr: string;
+}
+
+interface Semester {
+  pk: number;
+  year: number;
+  winter: boolean;
+}
+
+interface ImportResult {
+  login: string;
+  firstname: string;
+  lastname: string;
+  created: boolean;
+}
+
+interface Result {
+  users: ImportResult[];
+  count: number;
+  error?: string;
+}
+
+interface Teacher {
+  username: string;
+  full_name: string;
+  last_name: string;
+}
+
+interface ImportRequest {
+  semester_id: number;
+  subject_abbr: string;
+  activities: number[];
+  activities_to_teacher: { [key: number]: string };
+}
+
+const busy = ref<boolean>(false);
+
+const semesters = await loadSemesters();
+// Semester ID
+const semester = ref<number | null>(
+  semesters.length > 0 ? semesters[semesters.length - 1].pk : null
+);
+
+const subjects_kelvin = await loadKelvinSubjects();
+
+// Subject abbreviation
+const subject_kelvin_selected = ref<string | null>(
+  subjects_kelvin.length > 0 ? subjects_kelvin[0].abbr : null
+);
+
+const subjects_inbus_filtered = await loadInbusSubjects(subjects_kelvin);
+const subject_inbus_schedule = ref<ConcreteActivity[] | null>(null);
+
+const teachers = await loadTeachers();
+const activities_to_teacher_selected = ref({});
+
+const classes_to_import = ref([]);
+const result = ref<Result | null>(null);
+
+const canImport = computed(() => {
+  return (
+    classes_to_import.value.length && !busy.value && subject_kelvin_selected.value && semester.value
+  );
+});
+
+function assembleRequest(url: string): Request {
+  const headers: Headers = new Headers();
+
+  headers.set('Content-Type', 'application/json');
+  headers.set('Accept', 'application/json');
+
+  const request: Request = new Request(url, {
+    method: 'GET',
+    headers: headers
+  });
+
+  return request;
+}
+
+function svcc2num(svcc: string): number {
+  const [dept_code, version] = svcc.split('/');
+  const [dept, code] = dept_code.split('-');
+
+  const svcc_str = dept + code + version;
+  const svcc_num = Number(svcc_str);
+
+  return svcc_num;
+}
+
+function sortCollection<T>(collection: T[], key: string) {
+  collection.sort((a, b) => {
+    const name_a = a[key].toUpperCase();
+    const name_b = b[key].toUpperCase();
+    if (name_a < name_b) {
+      return -1;
+    }
+    if (name_a > name_b) {
+      return 1;
+    }
+    return 0;
+  });
+}
+
+async function loadKelvinSubjects(): Promise<KelvinSubject[]> {
+  const res = await fetch('/api/subjects/all', {});
+  const subjects_kelvin_resp = await res.json();
+
+  const subjects_kelvin: KelvinSubject[] = subjects_kelvin_resp.subjects;
+  sortCollection(subjects_kelvin, 'name');
+
+  return subjects_kelvin;
+}
+
+async function loadInbusSubjects(kelvin_subjects: KelvinSubject[]): Promise<InbusSubjectVersion[]> {
+  const res = await fetch('/api/inbus/subject_versions', {});
+
+  const subjects_inbus: InbusSubjectVersion[] = await res.json();
+  const subject_kelvin_abbrs = kelvin_subjects.map((s) => s.abbr);
+
+  const subjects_inbus_filtered = subjects_inbus.filter((subject_inbus) =>
+    subject_kelvin_abbrs.includes(subject_inbus.subject.abbrev)
+  );
+  subjects_inbus_filtered.sort(
+    (a, b) => svcc2num(a.subjectVersionCompleteCode) - svcc2num(b.subjectVersionCompleteCode)
+  );
+
+  return subjects_inbus_filtered;
+}
+
+function parseSemesters(semesters_data: Semester[]) {
+  const semesters = semesters_data.map((sm) => ({
+    pk: sm.pk,
+    year: sm.year,
+    winter: sm.winter,
+    display: String(sm.year) + (sm.winter ? 'W' : 'S')
+  }));
+
+  return semesters;
+}
+
+async function loadSemesters() {
+  const request = assembleRequest('/api/semesters');
+  const res = await fetch(request, {});
+
+  const semesters_data = await res.json();
+  return parseSemesters(semesters_data['semesters']);
+}
+
+async function loadScheduleForSubjectVersionId(subject_index: number) {
+  subject_inbus_schedule.value = null;
+  activities_to_teacher_selected.value = {};
+
+  const versionId = subjects_inbus_filtered[subject_index].subjectVersionId;
+  const request = assembleRequest(`/api/inbus/schedule/subject/version/${versionId}`);
+  const res = await fetch(request, {});
+  subject_inbus_schedule.value = await res.json();
+}
+
+async function loadTeachers(): Promise<Teacher[]> {
+  const request = assembleRequest('/api/teachers/all');
+  const res = await fetch(request, {});
+  const teachers_data = await res.json();
+  const teachers: Teacher[] = teachers_data['teachers'];
+
+  sortCollection(teachers, 'last_name');
+
+  return teachers;
+}
+
+function classesWithoutTeacher() {
+  const classes_without_teacher: number[] = [];
+  for (const clazz of subject_inbus_schedule.value) {
+    if (!clazz.teacherFullNames) {
+      classes_without_teacher.push(clazz.concreteActivityId);
+    }
+  }
+
+  return classes_without_teacher;
+}
+
+function isRequestValid(req) {
+  const classes_without_teacher = classesWithoutTeacher();
+
+  for (const activity of req.activities) {
+    if (classes_without_teacher.includes(activity)) {
+      if (!req.activities_to_teacher.hasOwnProperty(activity)) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+function getCorrespondingActivityRepr(activity_id: number) {
+  for (const ca of subject_inbus_schedule.value) {
+    if (ca.concreteActivityId === activity_id) {
+      return `${ca.educationTypeAbbrev}/${ca.order}, ${ca.subjectVersionCompleteCode}`;
+    }
+  }
+}
+
+function classesWithoutSelectedTeacher(req: ImportRequest) {
+  const classes_without_selected_teacher: string[] = [];
+  const classes_without_teacher = classesWithoutTeacher();
+  //console.log('classes_without_teacher', classes_without_teacher);
+
+  for (const activity_id of req.activities) {
+    if (classes_without_teacher.includes(activity_id)) {
+      if (!req.activities_to_teacher.hasOwnProperty(activity_id)) {
+        classes_without_selected_teacher.push(getCorrespondingActivityRepr(activity_id));
+      }
+    }
+  }
+
+  return classes_without_selected_teacher;
+}
+
+async function importActivities() {
+  busy.value = true;
+  const req: ImportRequest = {
+    semester_id: semester.value,
+    subject_abbr: subject_kelvin_selected.value,
+    activities: classes_to_import.value,
+    activities_to_teacher: activities_to_teacher_selected.value
+  };
+
+  if (!isRequestValid(req)) {
+    const classes_without_selected_teacher = classesWithoutSelectedTeacher(req);
+    const err_msg =
+      classes_without_selected_teacher.length > 1
+        ? `Selected classes to import (${classes_without_selected_teacher.join(', ')}) don't have assigned teacher. Please, select ones.`
+        : `Selected class to import (${classes_without_selected_teacher}) doesn't have assigned teacher. Please, select one.`;
+    result.value = { users: [], count: 0, error: err_msg };
+    busy.value = false;
+
+    return;
+  }
+
+  const res = await fetch('/api/import/activities', {
+    method: 'POST',
+    headers: {
+      Accept: 'application/json',
+      'Content-Type': 'application/json',
+      'X-CSRFToken': csrfToken()
+    },
+    body: JSON.stringify(req)
+  });
+
+  result.value = await res.json();
+  busy.value = false;
+}
+
+function onInbusSubjectSelected(event) {
+  const value: string = event.target.value;
+  if (value !== '') {
+    loadScheduleForSubjectVersionId(Number.parseInt(value));
+  } else {
+    subject_inbus_schedule.value = null;
+  }
+}
+
+function onTeacherSelected(event) {
+  const value: string = event.target.value;
+  const [activity_id, teacher_username] = value.split(',');
+
+  activities_to_teacher_selected.value[parseInt(activity_id)] = teacher_username;
+}
+</script>
+
+<template>
+  <select v-model="semester">
+    <option v-for="item in semesters" :key="item.pk" :value="item.pk">{{ item.display }}</option>
+  </select>
+
+  <select v-model="subject_kelvin_selected">
+    <option v-for="item in subjects_kelvin" :key="item.abbr" :value="item.abbr">
+      {{ item.abbr }} - {{ item.name }}
+    </option>
+  </select>
+
+  <select @change="onInbusSubjectSelected">
+    <option value="">Select Edison subject</option>
+    <option v-for="(item, index) in subjects_inbus_filtered" :key="index" :value="index">
+      {{ item.subjectVersionCompleteCode }} - {{ item.subject.abbrev }} - {{ item.subject.title }}
+    </option>
+  </select>
+
+  <table v-if="subject_inbus_schedule" class="table table-hover table-stripped table-sm">
+    <tbody>
+      <tr v-for="ca in subject_inbus_schedule" :key="ca.concreteActivityId">
+        <td>
+          <label>
+            <input
+              v-model="classes_to_import"
+              type="checkbox"
+              :value="ca.concreteActivityId"
+              name="classesToImport"
+            />
+            {{ ca.educationTypeAbbrev }}
+          </label>
+        </td>
+
+        <td>{{ ca.educationTypeAbbrev }}/{{ ca.order }}, {{ ca.subjectVersionCompleteCode }}</td>
+
+        <td>
+          <span v-if="ca.teacherFullNames">{{ ca.teacherFullNames }}</span>
+          <span v-else>
+            <select @change="onTeacherSelected">
+              <option value="">Select teacher</option>
+              <option
+                v-for="teacher in teachers"
+                :key="teacher.username"
+                :value="`${ca.concreteActivityId},${teacher.username}`"
+              >
+                {{ teacher.username }} - {{ teacher.full_name }}
+              </option>
+            </select>
+          </span>
+        </td>
+
+        <td>
+          {{ ca.weekDayAbbrev }}
+        </td>
+
+        <td>
+          {{ ca.beginTime }}
+        </td>
+
+        <td>
+          {{ ca.endTime }}
+        </td>
+      </tr>
+    </tbody>
+  </table>
+
+  <button
+    class="btn"
+    :class="{ 'btn-success': canImport, 'btn-danger': !canImport }"
+    :disabled="!canImport"
+    @click="importActivities"
+  >
+    <span v-if="busy">Importing...</span>
+    <span v-else>Import</span>
+  </button>
+
+  <div>
+    <div v-if="result">
+      <div v-if="result.error" class="alert alert-danger" role="alert">
+        {{ result.error }}
+      </div>
+      <div v-else>
+        <table class="table table-sm table-hover table-striped">
+          <thead>
+            <tr>
+              <th>Login</th>
+              <th>First name</th>
+              <th>Last name</th>
+              <th>User created</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="item in result.users" :key="item.login">
+              <td>{{ item.login }}</td>
+              <td>{{ item.firstname }}</td>
+              <td>{{ item.lastname }}</td>
+              <td>{{ item.created }}</td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped></style>
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 %}
+<kelvin-inbus-import></kelvin-inbus-import>
+{%  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/<int:task_id>/<str:login>/",
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 = {