diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py
index 5f8a865ff9c2e8dfe6a47ceedad30ac40421f246..6776cbcef22c105d28233688b1ff58581f153aa1 100644
--- a/smash/web/api_urls.py
+++ b/smash/web/api_urls.py
@@ -15,15 +15,25 @@ Including another URLconf
 """
 from django.conf.urls import url
 
-from web import api_views
+from web.api_views import worker, location, subject, appointment_type, appointment
 
 urlpatterns = [
-    url(r'^cities$', api_views.cities, name='web.api.cities'),
-    url(r'^countries$', api_views.countries, name='web.api.countries'),
-    url(r'^specializations$', api_views.specializations, name='web.api.specializations'),
-    url(r'^units$', api_views.units, name='web.api.units'),
-    url(r'^locations$', api_views.locations, name='web.api.locations'),
-    url(r'^referrals$', api_views.referrals, name='web.api.referrals'),
-    url(r'^appointment_types$', api_views.appointment_types, name='web.api.appointment_types'),
-    url(r'^subjects/(?P<type>[A-z]+)$', api_views.subjects, name='web.api.subjects'),
+    # appointments
+    url(r'^appointments/(?P<type>[A-z]+)$', appointment.appointments, name='web.api.appointments'),
+
+    # appointment types
+    url(r'^appointment_types$', appointment_type.appointment_types, name='web.api.appointment_types'),
+
+    # subjects data
+    url(r'^cities$', subject.cities, name='web.api.cities'),
+    url(r'^countries$', subject.countries, name='web.api.countries'),
+    url(r'^referrals$', subject.referrals, name='web.api.referrals'),
+    url(r'^subjects/(?P<type>[A-z]+)$', subject.subjects, name='web.api.subjects'),
+
+    # locations
+    url(r'^locations$', location.locations, name='web.api.locations'),
+
+    # worker data
+    url(r'^specializations$', worker.specializations, name='web.api.specializations'),
+    url(r'^units$', worker.units, name='web.api.units'),
 ]
diff --git a/smash/web/api_views/__init__.py b/smash/web/api_views/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e23cf4209db9249f6a3c07942770e0f89fee5a1c
--- /dev/null
+++ b/smash/web/api_views/__init__.py
@@ -0,0 +1,6 @@
+# coding=utf-8
+
+import appointment_type
+import location
+import subject
+import worker
diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py
new file mode 100644
index 0000000000000000000000000000000000000000..a31bb9f13dc84481e4c782a0d7c57f518e4cdeac
--- /dev/null
+++ b/smash/web/api_views/appointment.py
@@ -0,0 +1,109 @@
+import traceback
+
+from django.contrib.auth.decorators import login_required
+from django.http import JsonResponse
+from django.urls import reverse
+
+from web.models import Appointment
+from web.views import e500_error
+from web.views.appointment import APPOINTMENT_LIST_GENERIC, APPOINTMENT_LIST_UNFINISHED, APPOINTMENT_LIST_APPROACHING
+from web.views.notifications import get_filter_locations, \
+    get_today_midnight_date, \
+    get_unfinished_appointments
+
+
+@login_required
+def get_appointments(request, type, min_date, max_date):
+    if type == APPOINTMENT_LIST_GENERIC:
+        result = Appointment.objects.filter(location__in=get_filter_locations(request.user),
+                                            )
+    elif type == APPOINTMENT_LIST_UNFINISHED:
+        result = get_unfinished_appointments(request.user)
+    elif type == APPOINTMENT_LIST_APPROACHING:
+        result = Appointment.objects.filter(
+            datetime_when__gt=get_today_midnight_date(),
+            location__in=get_filter_locations(request.user),
+            status=Appointment.APPOINTMENT_STATUS_SCHEDULED
+        ).order_by("datetime_when")
+    else:
+        raise TypeError("Unknown query type: " + type)
+
+    if min_date is not None:
+        result = result.filter(datetime_when__gt=min_date)
+
+    if max_date is not None:
+        result = result.filter(datetime_when__lt=max_date)
+    return result.order_by("datetime_when")
+
+
+@login_required
+def appointments(request, type):
+    try:
+        # id of the query from dataTable: https://datatables.net/manual/server-side
+        draw = int(request.GET.get("draw", "-1"))
+
+        start = int(request.GET.get("start", "0"))
+        length = int(request.GET.get("length", "10"))
+
+        min_date = request.GET.get("start_date", None)
+        max_date = request.GET.get("end_date", None)
+
+        if min_date is not None:
+            length = 1000000000
+
+        all_appointments = get_appointments(request, type, min_date, max_date)
+
+        count = all_appointments.count()
+
+        sliced_subjects = all_appointments[start:(start + length)]
+
+        appointments = sliced_subjects
+
+        count_filtered = sliced_subjects.count()
+
+        data = []
+        for appointment in appointments:
+            data.append(serialize_appointment(appointment))
+
+        return JsonResponse({
+            "draw": draw,
+            "recordsTotal": count,
+            "recordsFiltered": count_filtered,
+            "data": data,
+        })
+    except:
+        traceback.print_exc()
+        return e500_error(request)
+
+
+def serialize_appointment(appointment):
+    subject = ""
+    if appointment.visit is not None:
+        title = appointment.visit.follow_up_title()
+        subject = appointment.visit.subject.first_name + " " + appointment.visit.subject.last_name + " (" + appointment.visit.subject.nd_number + ")"
+    else:
+        title = appointment.comment
+
+    type = ""
+    for appointment_type in appointment.appointment_types.all():
+        type += appointment_type.code + ", "
+    time = ""
+    if appointment.datetime_when is not None:
+        time = appointment.datetime_when.strftime('%Y-%m-%d %H:%M')
+        # time = appointment.datetime_when.strftime('%Y-%m-%d %H:%M:%S.%f %Z%z')
+    until = ""
+    if appointment.datetime_when is not None:
+        until = appointment.datetime_until().strftime('%Y-%m-%d %H:%M')
+
+    result = {
+        "subject": subject,
+        "title": title,
+        "type": type,
+        "datetime_when": time,
+        "datetime_until": until,
+        "comment": appointment.comment,
+        "color": appointment.color(),
+        "id": appointment.id,
+        "url": reverse('web.views.appointment_edit', kwargs={'id': str(appointment.id)})
+    }
+    return result
diff --git a/smash/web/api_views/appointment_type.py b/smash/web/api_views/appointment_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..66f9af836adc5cd2f17cbd1a35cbd974fc6fa840
--- /dev/null
+++ b/smash/web/api_views/appointment_type.py
@@ -0,0 +1,20 @@
+from django.contrib.auth.decorators import login_required
+from django.http import JsonResponse
+
+from web.models import AppointmentType
+
+
+@login_required
+def appointment_types(request):
+    appointments = AppointmentType.objects.filter().all()
+    result = []
+    for appointment in appointments:
+        result.append({
+            "id": appointment.id,
+            "type": appointment.code,
+            "default_duration": appointment.default_duration,
+            "can_be_parallelized": appointment.can_be_parallelized,
+        })
+    return JsonResponse({
+        "appointment_types": result
+    })
diff --git a/smash/web/api_views/location.py b/smash/web/api_views/location.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5da64abfea495e4340409fb87012254bcf46e99
--- /dev/null
+++ b/smash/web/api_views/location.py
@@ -0,0 +1,15 @@
+from django.contrib.auth.decorators import login_required
+from django.http import JsonResponse
+
+from web.models import Location
+
+
+@login_required
+def locations(request):
+    locations = Location.objects.all()
+    data = []
+    for location in locations:
+        data.append({"id": location.id, "name": location.name})
+    return JsonResponse({
+        "locations": data
+    })
diff --git a/smash/web/api_views.py b/smash/web/api_views/subject.py
similarity index 79%
rename from smash/web/api_views.py
rename to smash/web/api_views/subject.py
index 1d56a1fd720ecab3f7bc55077119bc6ce9490291..9368d26c2cfbdd89c16876695b29501fc8e26469 100644
--- a/smash/web/api_views.py
+++ b/smash/web/api_views/subject.py
@@ -1,10 +1,10 @@
 from django.contrib.auth.decorators import login_required
 from django.http import JsonResponse
 
-from models import Subject, Worker, AppointmentType, Location
-from views import e500_error
-from views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
-from views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder
+from web.models import Subject
+from web.views import e500_error
+from web.views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder
+from web.views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
 
 
 @login_required
@@ -15,17 +15,6 @@ def cities(request):
     })
 
 
-@login_required
-def locations(request):
-    locations = Location.objects.all()
-    data = []
-    for location in locations:
-        data.append({"id": location.id, "name": location.name})
-    return JsonResponse({
-        "locations": data
-    })
-
-
 @login_required
 def countries(request):
     X = Subject.objects.filter(country__isnull=False).values_list('country').distinct()
@@ -42,22 +31,6 @@ def referrals(request):
     })
 
 
-@login_required
-def specializations(request):
-    X = Worker.objects.filter(specialization__isnull=False).values_list('specialization').distinct()
-    return JsonResponse({
-        "specializations": [x[0] for x in X]
-    })
-
-
-@login_required
-def units(request):
-    X = Worker.objects.filter(unit__isnull=False).values_list('unit').distinct()
-    return JsonResponse({
-        "units": [x[0] for x in X]
-    })
-
-
 @login_required
 def get_subjects(request, type):
     if type == SUBJECT_LIST_GENERIC:
@@ -170,6 +143,7 @@ def subjects(request, type):
     except:
         return e500_error(request)
 
+
 def get_yes_no(val):
     if val:
         return "YES"
@@ -194,19 +168,3 @@ def serialize_subject(subject):
         "id": subject.id,
     }
     return result
-
-
-@login_required
-def appointment_types(request):
-    appointments = AppointmentType.objects.filter().all()
-    result = []
-    for appointment in appointments:
-        result.append({
-            "id": appointment.id,
-            "type": appointment.code,
-            "default_duration": appointment.default_duration,
-            "can_be_parallelized": appointment.can_be_parallelized,
-        })
-    return JsonResponse({
-        "appointment_types": result
-    })
diff --git a/smash/web/api_views/worker.py b/smash/web/api_views/worker.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c141e25dd9e32e08d8a2523b4804a2e03f726a7
--- /dev/null
+++ b/smash/web/api_views/worker.py
@@ -0,0 +1,20 @@
+from django.contrib.auth.decorators import login_required
+from django.http import JsonResponse
+
+from web.models import Worker
+
+
+@login_required
+def specializations(request):
+    X = Worker.objects.filter(specialization__isnull=False).values_list('specialization').distinct()
+    return JsonResponse({
+        "specializations": [x[0] for x in X]
+    })
+
+
+@login_required
+def units(request):
+    X = Worker.objects.filter(unit__isnull=False).values_list('unit').distinct()
+    return JsonResponse({
+        "units": [x[0] for x in X]
+    })
diff --git a/smash/web/static/js/appointment.js b/smash/web/static/js/appointment.js
index 845958ae381dbe6e03f75ad2146e14af3bdf1b22..852d14ca57b1b6fd8f66c4a7afb4bdbf52fe34bf 100644
--- a/smash/web/static/js/appointment.js
+++ b/smash/web/static/js/appointment.js
@@ -88,5 +88,43 @@ function appointment_type_behaviour(checkboxes, outObject, api_call) {
         }
 
     });
-
 }
+
+function get_calendar_events_function(source, allow_url_redirection) {
+    if (allow_url_redirection === undefined) {
+        allow_url_redirection = false;
+    }
+    return function (start, end, timezone, callback) {
+        $.ajax({
+            data: {
+                // our hypothetical feed requires UNIX timestamps
+                start_date: start.format(),
+                end_date: end.format()
+            },
+            url: source,
+            success: function (doc) {
+                var events = [];
+                for (var i = 0; i < doc.data.length; i++) {
+                    var title = doc.data[i].subject;
+                    if (title != "") {
+                        title += "; type: " + doc.data[i].type;
+                    } else {
+                        title = doc.data[i].title
+                    }
+                    var event = {
+                        title: title,
+                        start: doc.data[i].datetime_when,
+                        end: doc.data[i].datetime_until,
+                        id: doc.data[i].id,
+                        color: doc.data[i].color,
+                    };
+                    if (allow_url_redirection) {
+                        event["url"] = doc.data[i].url;
+                    }
+                    events.push(event)
+                }
+                callback(events);
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/smash/web/templates/appointments/add.html b/smash/web/templates/appointments/add.html
index 419e28dc1ab5e1cdbf1f6c24c5b952e8985fd0f3..8dcd32ef4be051c4ae1c4624c3f1e6dc95203fa5 100644
--- a/smash/web/templates/appointments/add.html
+++ b/smash/web/templates/appointments/add.html
@@ -136,18 +136,7 @@
                     document.getElementById("id_datetime_when").value = dateString;
 
                 },
-                events: [
-                    {% for appointment in full_appointment_list %}
-                        {
-                            title: '{{ appointment.title }}',
-                            start: '{{ appointment.datetime_when | date:"c" }}',
-                            end: '{{ appointment.datetime_until | date:"c" }}',
-                            color: '{{ appointment.color }}',
-                            subject_id: '{{ appointment.visit.subject.id }}',
-                            id: '{{ appointment.id }}'
-                        },
-                    {% endfor %}
-                ],
+                events: get_calendar_events_function("{% url 'web.api.appointments' full_list %}", false),
             });
 
         });
diff --git a/smash/web/templates/appointments/index.html b/smash/web/templates/appointments/index.html
index 09b901ea52a7e62ba4f27e12cec547610161de7f..66aa979771b9489aa2cf7f065dc700b76a6e7454 100644
--- a/smash/web/templates/appointments/index.html
+++ b/smash/web/templates/appointments/index.html
@@ -24,53 +24,20 @@
 {% block maincontent %}
     <div class="row">
         <div class="col-md-5">
-            {% if approaching_list %}
-                <table id="approaching_table" class="table table-bordered table-striped">
-                    <thead>
-                    <tr>
-                        <th>Subject</th>
-                        <th>Visit</th>
-                        <th>Type</th>
-                        <th>Date</th>
-                        <th>Details</th>
-                    </tr>
-                    </thead>
-                    <tbody>
+            <table id="approaching_table" class="table table-bordered table-striped">
+                <thead>
+                <tr>
+                    <th>Subject</th>
+                    <th>Visit</th>
+                    <th>Type</th>
+                    <th>Date</th>
+                    <th>Details</th>
+                </tr>
+                </thead>
+                <tbody>
+                </tbody>
+            </table>
 
-                    {% for approach in approaching_list %}
-                        <tr>
-                            {% if approach.visit == None %}
-                                <td>
-                                </td>
-                                <td>
-                                </td>
-                            {% else %}
-                                <td>
-                                    {{ approach.visit.subject.first_name }} {{ approach.visit.subject.last_name }}
-                                    ({{ approach.visit.subject.nd_number }})
-                                </td>
-                                <td>
-                                    {{ approach.visit.follow_up_title }}
-                                </td>
-                            {% endif %}
-                            <td>
-                                {% for type in approach.appointment_types.all %}
-                                    {{ type.code }},
-                                {% endfor %}
-                            </td>
-                            <td>{{ approach.datetime_when | date:"Y-m-d H:i" }}</td>
-                            <td>
-                                <a href="{% url 'web.views.appointment_edit' approach.id %}" type="button"
-                                   class="btn btn-block btn-default">Details</a>
-                            </td>
-                        </tr>
-                    {% endfor %}
-                    </tbody>
-                </table>
-
-            {% else %}
-                <p>No visits approaching in close future.</p>
-            {% endif %}
         </div>
 
         <div class="col-md-7">
@@ -98,19 +65,36 @@
     <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
     <script src="{% static 'AdminLTE/plugins/moment.js/moment.min.js' %}"></script>
     <script src="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.js' %}"></script>
+    <script src="{% static 'js/appointment.js' %}"></script>
 
     <script>
         $(function () {
-            $('#planning_table, #approaching_table').DataTable({
-                "paging": true,
-                "lengthChange": false,
-                "searching": true,
-                "ordering": true,
-                "order": [[3, "asc"]],
-                "info": true,
-                "autoWidth": false
+            var table = $('#approaching_table').DataTable({
+                serverSide: true,
+                processing: true,
+                ordering: false,
+                ajax: "{% url 'web.api.appointments' approaching_list %}",
+                columns: [
+                    {"data": "subject"},
+                    {"data": "title"},
+                    {"data": "type"},
+                    {"data": "datetime_when"},
+                    {"data": null},
+                ],
+                columnDefs: [{
+                    "targets": 4,
+                    "data": "id",
+                    "defaultContent": '<a href="#" type="button" class="btn btn-block btn-default">Edit</a>'
+                }]
+            })
+            $('#approaching_table tbody').on('click', 'a', function () {
+                var data = table.row($(this).parents('tr')).data();
+                var url = "{% url 'web.views.appointment_edit' 12345 %}".replace(/12345/, data.id.toString());
+                window.location.href = url;
             });
 
+            $('#approaching_table_filter').css("display", "none");
+
             $('#calendar').fullCalendar({
                 header: {
                     left: 'prev,next today',
@@ -119,19 +103,9 @@
                 },
                 editable: false,
                 weekNumbers: true,
-                events: [
-                    {% for appointment in full_list %}
-                        {
-                            title: '{{ appointment.title }}',
-                            start: '{{ appointment.datetime_when | date:"c" }}',
-                            end: '{{ appointment.datetime_until | date:"c" }}',
-                            color: '{{ appointment.color }}',
-                            subject_id: '{{ appointment.visit.subject.id }}',
-                            id: '{{ appointment.id }}',
-                            url: '{% url 'web.views.appointment_edit' appointment.id %}',
-                        },
-                    {% endfor %}
-                ],
+                startParam: "start_date",
+                endParam: "end_date",
+                events: get_calendar_events_function("{% url 'web.api.appointments' full_list %}", false),
             });
         });
     </script>
diff --git a/smash/web/templates/appointments/list.html b/smash/web/templates/appointments/list.html
index 782f8d5ba79e2aa93e8361023872a950f9acad2c..b20d99c7b0dfcf3dee0d1eb9609ae3902b84faf0 100644
--- a/smash/web/templates/appointments/list.html
+++ b/smash/web/templates/appointments/list.html
@@ -6,9 +6,6 @@
     <!-- DataTables -->
     <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
 
-    <!-- fullCalendar 2.2.5-->
-    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.min.css' %}">
-    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/fullcalendar/fullcalendar.print.css' %}" media="print">
 {% endblock styles %}
 
 {% block ui_active_tab %}'appointments'{% endblock ui_active_tab %}
@@ -24,7 +21,7 @@
 {% block maincontent %}
     <div class="row">
         <div class="col-md-16">
-            <table id="approaching_table" class="table table-bordered table-striped">
+            <table id="table" class="table table-bordered table-striped">
                 <thead>
                 <tr>
                     <th>Subject</th>
@@ -32,41 +29,70 @@
                     <th>Type</th>
                     <th>Date</th>
                     <th>Details</th>
+                    <th>Edit</th>
                 </tr>
                 </thead>
                 <tbody>
-
-                {% for approach in appointment_list %}
-                    <tr>
-                        {% if approach.visit  == None %}
-                            <td>
-                            </td>
-                            <td>
-                            </td>
-                        {% else %}
-                            <td>
-                                {{ approach.visit.subject.first_name }} {{ approach.visit.subject.last_name }}
-                                ({{ approach.visit.subject.nd_number }})
-                            </td>
-                            <td>
-                                {{ approach.visit.follow_up_title }}
-                            </td>
-                        {% endif %}
-                        <td>
-                            {% for type in approach.appointment_types.all %}
-                                {{ type.code }},
-                            {% endfor %}
-                        </td>
-                        <td>{{ approach.datetime_when | date:"Y-m-d H:i" }}</td>
-                        <td>
-                            <a href="{% url 'web.views.appointment_edit' approach.id %}" type="button"
-                               class="btn btn-block btn-default">Details</a>
-                        </td>
-                    </tr>
-                {% endfor %}
                 </tbody>
+                <tfoot style="display: table-header-group;"/>
             </table>
 
         </div>
     </div>
 {% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
+    <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
+
+    <script>
+        $(function () {
+            var table = $('#table').DataTable({
+                serverSide: true,
+                processing: true,
+                ordering: false,
+                ajax: "{% url 'web.api.appointments' list_type %}",
+                columns: [
+                    {"data": "subject"},
+                    {"data": "title"},
+                    {"data": "type"},
+                    {"data": "datetime_when"},
+                    {"data": "comment"},
+                    {"data": null},
+                ],
+                columnDefs: [{
+                    "targets": 5,
+                    "data": "id",
+                    "defaultContent": '<a href="#" type="button" class="btn btn-block btn-default">Edit</a>'
+                },
+{#                    {#}
+{#                        render: function (data, type, row) {#}
+{#                            var date = new Date(data);#}
+{#                            var mm = date.getMonth() + 1; // getMonth() is zero-based#}
+{#                            var dd = date.getDate();#}
+{#                            var hour = date.getHours();#}
+{#                            var minute = date.getMinutes();#}
+{##}
+{#                            return [date.getFullYear(),#}
+{#                                    (mm > 9 ? '' : '0') + mm,#}
+{#                                    (dd > 9 ? '' : '0') + dd#}
+{#                                ].join('-') + " " + hour + ":" + minute;#}
+{#                        },#}
+{#                        targets: 3#}
+{#                    }#}
+                ]
+            });
+
+            $('#table tbody').on('click', 'a', function () {
+                var data = table.row($(this).parents('tr')).data();
+                var url = "{% url 'web.views.appointment_edit' 12345 %}".replace(/12345/, data.id.toString());
+                window.location.href = url;
+            });
+
+            $('#table_filter').css("display", "none");
+        });
+    </script>
+
+{% endblock scripts %}
diff --git a/smash/web/tests/test_api_appointment.py b/smash/web/tests/test_api_appointment.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf28f7c757745e3c1afbe8fa1853c47a58e04b1f
--- /dev/null
+++ b/smash/web/tests/test_api_appointment.py
@@ -0,0 +1,100 @@
+# coding=utf-8
+
+import datetime
+
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+
+from web.tests.functions import create_subject, create_worker, create_visit, create_appointment, \
+    create_appointment_type, create_get_suffix
+from web.views.appointment import APPOINTMENT_LIST_GENERIC, APPOINTMENT_LIST_APPROACHING, APPOINTMENT_LIST_UNFINISHED
+from web.views.notifications import get_today_midnight_date
+
+
+class TestApi(TestCase):
+    def setUp(self):
+        self.subject = create_subject()
+        self.client = Client()
+        username = 'piotr'
+        password = 'top_secret'
+        self.user = User.objects.create_user(
+            username=username, email='jacob@bla', password=password)
+        self.worker = create_worker(self.user)
+        self.client.login(username=username, password=password)
+
+    def test_appointments_invalid(self):
+        response = self.client.get(reverse('web.api.appointments', kwargs={'type': "bla"}))
+        self.assertEqual(response.status_code, 500)
+
+    def test_appointments_valid(self):
+        name = "Piotrek"
+        self.subject.first_name = name
+        self.subject.save()
+        visit = create_visit(self.subject)
+        create_appointment(visit)
+        appointment2 = create_appointment(visit, get_today_midnight_date())
+        appointment2.visit = None
+        appointment2.appointment_types.add(create_appointment_type())
+        appointment2.save()
+
+        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_GENERIC})
+        response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(name in response.content)
+
+    def test_appointments_approaching(self):
+        name = "Piotrek"
+        self.subject.first_name = name
+        self.subject.save()
+        visit = create_visit(self.subject)
+        create_appointment(visit, get_today_midnight_date() + datetime.timedelta(days=2))
+
+        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_APPROACHING})
+        response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(name in response.content)
+
+    def test_appointments_unfinished(self):
+        name = "Piotrek"
+        self.subject.first_name = name
+        self.subject.save()
+        visit = create_visit(self.subject)
+        create_appointment(visit, get_today_midnight_date() + datetime.timedelta(days=-12))
+
+        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_UNFINISHED})
+        response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(name in response.content)
+
+    def test_get_calendar_appointments(self):
+        name = "Peter"
+        self.subject.first_name = name
+        self.subject.save()
+        visit = create_visit(self.subject)
+        appointment = create_appointment(visit, get_today_midnight_date())
+        appointment.appointment_types.add(create_appointment_type())
+        appointment.save()
+
+        params = {
+            "start_date": (get_today_midnight_date() + datetime.timedelta(days=2)).strftime("%Y-%m-%d"),
+            "end_date": (get_today_midnight_date() + datetime.timedelta(days=3)).strftime("%Y-%m-%d"),
+        }
+        url = ("%s" + create_get_suffix(params)) % reverse('web.api.appointments',
+                                                           kwargs={'type': APPOINTMENT_LIST_GENERIC})
+        response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertFalse(name in response.content)
+
+        params["start_date"] = (get_today_midnight_date() + datetime.timedelta(days=-2)).strftime("%Y-%m-%d")
+        url = ("%s" + create_get_suffix(params)) % reverse('web.api.appointments',
+                                                           kwargs={'type': APPOINTMENT_LIST_GENERIC})
+        response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertTrue(name in response.content)
diff --git a/smash/web/tests/test_api_appointment_type.py b/smash/web/tests/test_api_appointment_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..24b06c1feda1ac4e0f6b20237fcd38147f00b529
--- /dev/null
+++ b/smash/web/tests/test_api_appointment_type.py
@@ -0,0 +1,40 @@
+# coding=utf-8
+import json
+
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+
+from web.tests.functions import create_worker, create_appointment_type
+
+
+class TestApi(TestCase):
+    def setUp(self):
+        self.client = Client()
+        username = 'piotr'
+        password = 'top_secret'
+        self.user = User.objects.create_user(
+            username=username, email='jacob@bla', password=password)
+        self.worker = create_worker(self.user)
+        self.client.login(username=username, password=password)
+
+    def test_appointment_types(self):
+        type_name = "some type name"
+
+        response = self.client.get(reverse('web.api.appointment_types'))
+        self.assertEqual(response.status_code, 200)
+
+        self.appointment_type = create_appointment_type()
+        self.appointment_type.code = type_name
+        self.appointment_type.save()
+
+        response = self.client.get(reverse('web.api.appointment_types'))
+        appointment_types = json.loads(response.content)['appointment_types']
+
+        found = False
+        for type in appointment_types:
+            if type['type'] == type_name:
+                found = True
+
+        self.assertTrue(found)
diff --git a/smash/web/tests/test_api_location.py b/smash/web/tests/test_api_location.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd7c55167b75022e929324430344271ad8fac5de
--- /dev/null
+++ b/smash/web/tests/test_api_location.py
@@ -0,0 +1,38 @@
+# coding=utf-8
+import json
+
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+
+from web.tests.functions import create_worker, create_location
+
+
+class TestApi(TestCase):
+    def setUp(self):
+        self.client = Client()
+        username = 'piotr'
+        password = 'top_secret'
+        self.user = User.objects.create_user(
+            username=username, email='jacob@bla', password=password)
+        self.worker = create_worker(self.user)
+        self.client.login(username=username, password=password)
+
+    def test_locations(self):
+        location_name = "some location"
+
+        response = self.client.get(reverse('web.api.locations'))
+        self.assertEqual(response.status_code, 200)
+
+        create_location(location_name)
+
+        response = self.client.get(reverse('web.api.locations'))
+        locations = json.loads(response.content)['locations']
+
+        found = False
+        for location in locations:
+            if location['name'] == location_name:
+                found = True
+
+        self.assertTrue(found)
diff --git a/smash/web/tests/test_api.py b/smash/web/tests/test_api_subject.py
similarity index 76%
rename from smash/web/tests/test_api.py
rename to smash/web/tests/test_api_subject.py
index f032b1d602d107f30e1e410d4ac9f0c3a26256a0..9a947b410031fe06d9fd96e3ed113f7b8647d8f2 100644
--- a/smash/web/tests/test_api.py
+++ b/smash/web/tests/test_api_subject.py
@@ -1,21 +1,27 @@
 # coding=utf-8
 import json
 
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
 from django.urls import reverse
 
-from web.api_views import get_subjects_order, get_subjects_filtered, serialize_subject
+from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject, SUBJECT_LIST_GENERIC, \
+    SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
 from web.models import Subject
-from web.tests.functions import create_location, \
-    create_get_suffix
-from web.tests.functions import create_subject, create_appointment_type
-from web.views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
-from . import LoggedInWithWorkerTestCase
+from web.tests.functions import create_subject, create_worker, create_get_suffix
 
 
-class TestApi(LoggedInWithWorkerTestCase):
+class TestApi(TestCase):
     def setUp(self):
-        super(TestApi, self).setUp()
         self.subject = create_subject()
+        self.client = Client()
+        username = 'piotr'
+        password = 'top_secret'
+        self.user = User.objects.create_user(
+            username=username, email='jacob@bla', password=password)
+        self.worker = create_worker(self.user)
+        self.client.login(username=username, password=password)
 
     def test_cities(self):
         city_name = "some city"
@@ -35,24 +41,6 @@ class TestApi(LoggedInWithWorkerTestCase):
 
         self.assertTrue(city_name in cities)
 
-    def test_locations(self):
-        location_name = "some location"
-
-        response = self.client.get(reverse('web.api.locations'))
-        self.assertEqual(response.status_code, 200)
-
-        location = create_location(location_name)
-
-        response = self.client.get(reverse('web.api.locations'))
-        appointment_types = json.loads(response.content)['locations']
-
-        found = False
-        for type in appointment_types:
-            if type['name'] == location_name:
-                found = True
-
-        self.assertTrue(found)
-
     def test_countries(self):
         country_name = "some country"
 
@@ -71,42 +59,6 @@ class TestApi(LoggedInWithWorkerTestCase):
 
         self.assertTrue(country_name in countries)
 
-    def test_specializations(self):
-        specialization_name = "some spec"
-
-        response = self.client.get(reverse('web.api.specializations'))
-        self.assertEqual(response.status_code, 200)
-
-        specializations = json.loads(response.content)['specializations']
-
-        self.assertFalse(specialization_name in specializations)
-
-        self.worker.specialization = specialization_name
-        self.worker.save()
-
-        response = self.client.get(reverse('web.api.specializations'))
-        specializations = json.loads(response.content)['specializations']
-
-        self.assertTrue(specialization_name in specializations)
-
-    def test_units(self):
-        unit_name = "some unit"
-
-        response = self.client.get(reverse('web.api.units'))
-        self.assertEqual(response.status_code, 200)
-
-        units = json.loads(response.content)['units']
-
-        self.assertFalse(unit_name in units)
-
-        self.worker.unit = unit_name
-        self.worker.save()
-
-        response = self.client.get(reverse('web.api.units'))
-        units = json.loads(response.content)['units']
-
-        self.assertTrue(unit_name in units)
-
     def test_referrals(self):
         referral_name = "some referral"
 
@@ -125,26 +77,6 @@ class TestApi(LoggedInWithWorkerTestCase):
 
         self.assertTrue(referral_name in referrals)
 
-    def test_appointment_types(self):
-        type_name = "some type name"
-
-        response = self.client.get(reverse('web.api.appointment_types'))
-        self.assertEqual(response.status_code, 200)
-
-        self.appointment_type = create_appointment_type()
-        self.appointment_type.code = type_name
-        self.appointment_type.save()
-
-        response = self.client.get(reverse('web.api.appointment_types'))
-        appointment_types = json.loads(response.content)['appointment_types']
-
-        found = False
-        for type in appointment_types:
-            if type['type'] == type_name:
-                found = True
-
-        self.assertTrue(found)
-
     def test_subjects_general(self):
         response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC}))
         self.assertEqual(response.status_code, 200)
diff --git a/smash/web/tests/test_api_worker.py b/smash/web/tests/test_api_worker.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3f353d3b4fbd2a45dd4703545dc8269b5f2c617
--- /dev/null
+++ b/smash/web/tests/test_api_worker.py
@@ -0,0 +1,56 @@
+# coding=utf-8
+import json
+
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+
+from web.tests.functions import create_subject, create_worker
+
+
+class TestApi(TestCase):
+    def setUp(self):
+        self.client = Client()
+        username = 'piotr'
+        password = 'top_secret'
+        self.user = User.objects.create_user(
+            username=username, email='jacob@bla', password=password)
+        self.worker = create_worker(self.user)
+        self.client.login(username=username, password=password)
+
+    def test_specializations(self):
+        specialization_name = "some spec"
+
+        response = self.client.get(reverse('web.api.specializations'))
+        self.assertEqual(response.status_code, 200)
+
+        specializations = json.loads(response.content)['specializations']
+
+        self.assertFalse(specialization_name in specializations)
+
+        self.worker.specialization = specialization_name
+        self.worker.save()
+
+        response = self.client.get(reverse('web.api.specializations'))
+        specializations = json.loads(response.content)['specializations']
+
+        self.assertTrue(specialization_name in specializations)
+
+    def test_units(self):
+        unit_name = "some unit"
+
+        response = self.client.get(reverse('web.api.units'))
+        self.assertEqual(response.status_code, 200)
+
+        units = json.loads(response.content)['units']
+
+        self.assertFalse(unit_name in units)
+
+        self.worker.unit = unit_name
+        self.worker.save()
+
+        response = self.client.get(reverse('web.api.units'))
+        units = json.loads(response.content)['units']
+
+        self.assertTrue(unit_name in units)
diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py
index c13a07129603005fb0a2d6cb508573299822d5cb..8bc59f0c2d1b637b96abe4c12ba5966c2dea6732 100644
--- a/smash/web/views/appointment.py
+++ b/smash/web/views/appointment.py
@@ -1,34 +1,27 @@
 # coding=utf-8
 from django.shortcuts import get_object_or_404, redirect
 
-from notifications import get_today_midnight_date, get_filter_locations, get_calendar_full_appointments, \
-    get_unfinished_appointments
 from . import wrap_response
 from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, SubjectEditForm
 from ..models import Appointment
 
+APPOINTMENT_LIST_GENERIC = "GENERIC"
+APPOINTMENT_LIST_UNFINISHED = "UNFINISHED"
+APPOINTMENT_LIST_APPROACHING = "APPROACHING"
 
-def appointments(request):
-    approaching_list = Appointment.objects.filter(
-        datetime_when__gt=get_today_midnight_date(),
-        location__in=get_filter_locations(request.user),
-        status=Appointment.APPOINTMENT_STATUS_SCHEDULED
-    ).order_by('datetime_when')
-
-    full_list = get_calendar_full_appointments(request.user)
 
+def appointments(request):
     context = {
-        'approaching_list': approaching_list,
-        'full_list': full_list
+        'approaching_list': APPOINTMENT_LIST_APPROACHING,
+        'full_list': APPOINTMENT_LIST_GENERIC
     }
 
     return wrap_response(request, "appointments/index.html", context)
 
 
 def unfinished_appointments(request):
-    appointments = get_unfinished_appointments(request.user)
     context = {
-        'appointment_list': appointments,
+        'list_type': APPOINTMENT_LIST_UNFINISHED,
     }
 
     return wrap_response(request, "appointments/list.html", context)
@@ -41,7 +34,6 @@ def appointment_details(request, id):
 
 
 def appointment_add(request, visit_id=None):
-    full_list = get_calendar_full_appointments(request.user)
     if request.method == 'POST':
         form = AppointmentAddForm(request.POST, request.FILES, user=request.user)
         if form.is_valid():
@@ -55,7 +47,7 @@ def appointment_add(request, visit_id=None):
         form = AppointmentAddForm(user=request.user)
 
     return wrap_response(request, 'appointments/add.html',
-                         {'form': form, 'visitID': visit_id, 'full_appointment_list': full_list})
+                         {'form': form, 'visitID': visit_id, 'full_list': APPOINTMENT_LIST_GENERIC})
 
 
 def appointment_edit(request, id):
@@ -84,7 +76,7 @@ def appointment_edit(request, id):
                 subject_form.save()
             the_appointment = get_object_or_404(Appointment, id=id)
             if (the_appointment.status != Appointment.APPOINTMENT_STATUS_SCHEDULED) and (
-                the_appointment.visit is not None):
+                        the_appointment.visit is not None):
                 return redirect('web.views.visit_details', id=the_appointment.visit.id)
             else:
                 return redirect('web.views.appointments')
diff --git a/smash/web/views/notifications.py b/smash/web/views/notifications.py
index 0c57e769614b0c18076a4bc6079520af0a4ed79d..31f749d8f8997c25ada97ffa4f2043b059f2ce21 100644
--- a/smash/web/views/notifications.py
+++ b/smash/web/views/notifications.py
@@ -241,12 +241,4 @@ def get_filter_locations(user):
 def get_today_midnight_date():
     today = datetime.datetime.now()
     today_midnight = datetime.datetime(today.year, today.month, today.day)
-    return today_midnight
-
-
-def get_calendar_full_appointments(user):
-    month_ago = get_today_midnight_date() + datetime.timedelta(days=-31)
-    return Appointment.objects.filter(
-        datetime_when__gt=month_ago,
-        location__in=get_filter_locations(user),
-    ).order_by('datetime_when')
+    return today_midnight
\ No newline at end of file