diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py
index 08be1500d63dfb7898b9a940659f217ece424973..56f9dc4f46cbac9ca9c7ab622abd000aadbe9aa5 100644
--- a/smash/web/api_urls.py
+++ b/smash/web/api_urls.py
@@ -20,7 +20,9 @@ from web.api_views import worker, location, subject, appointment_type, appointme
 
 urlpatterns = [
     # appointments
-    url(r'^appointments/(?P<type>[A-z]+)$', appointment.appointments, name='web.api.appointments'),
+    url(r'^appointments/(?P<appointment_type>[A-z]+)$', appointment.appointments, name='web.api.appointments'),
+    url(r'^appointments:columns/(?P<appointment_list_type>[A-z]+)$', appointment.get_appointment_columns,
+        name='web.api.appointments.columns'),
 
     # appointment types
     url(r'^appointment_types$', appointment_type.appointment_types, name='web.api.appointment_types'),
diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py
index 331c331c50b6cecc4f17070235448222c07d7dda..2ef29f8d941f9333ba3fa9790bafddf4a043742c 100644
--- a/smash/web/api_views/appointment.py
+++ b/smash/web/api_views/appointment.py
@@ -6,31 +6,55 @@ from django.http import JsonResponse
 from django.urls import reverse
 from django.utils import timezone
 
-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
+from web.api_views.serialization_utils import serialize_datetime, location_to_str, flying_team_to_str, add_column, \
+    bool_to_yes_no, get_filters_for_data_table_request
+from web.models import Appointment, Study, SubjectColumns, AppointmentColumns, AppointmentList
+from web.models.appointment_list import APPOINTMENT_LIST_GENERIC, APPOINTMENT_LIST_UNFINISHED, \
+    APPOINTMENT_LIST_APPROACHING
+from web.models.constants import GLOBAL_STUDY_ID
+from web.views.notifications import get_filter_locations, get_today_midnight_date, get_unfinished_appointments
 
 logger = logging.getLogger(__name__)
 
 
 @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:
+def get_appointment_columns(request, appointment_list_type):
+    study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+    appointment_lists = AppointmentList.objects.filter(study=study, type=appointment_list_type)
+    if len(appointment_lists) > 0:
+        appointment_list = appointment_lists[0]
+        subject_columns = appointment_list.visible_subject_columns
+        appointment_columns = appointment_list.visible_appointment_columns
+    else:
+        subject_columns = SubjectColumns()
+        appointment_columns = AppointmentColumns()
+
+    result = []
+    add_column(result, "First name", "first_name", subject_columns, "string_filter")
+    add_column(result, "Last name", "last_name", subject_columns, "string_filter")
+    add_column(result, "Info sent", "post_mail_sent", appointment_columns, "yes_no_filter")
+    add_column(result, "Date", "datetime_when", appointment_columns, None)
+    add_column(result, "Appointment types", "appointment_types", appointment_columns, "appointment_type_filter",
+               sortable=False)
+    add_column(result, "Edit", "edit", None, None, sortable=False)
+
+    return JsonResponse({"columns": result})
+
+
+@login_required
+def get_appointments(request, appointment_type, min_date, max_date):
+    if appointment_type == APPOINTMENT_LIST_GENERIC:
+        result = Appointment.objects.filter(location__in=get_filter_locations(request.user))
+    elif appointment_type == APPOINTMENT_LIST_UNFINISHED:
         result = get_unfinished_appointments(request.user)
-    elif type == APPOINTMENT_LIST_APPROACHING:
+    elif appointment_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)
+        raise TypeError("Unknown query type: " + appointment_type)
 
     if min_date is not None:
         min_date = datetime.strptime(min_date, "%Y-%m-%d").replace(tzinfo=timezone.now().tzinfo)
@@ -42,74 +66,126 @@ def get_appointments(request, type, min_date, max_date):
     return result.order_by("datetime_when")
 
 
+def get_appointments_order(appointments_to_be_ordered, order_column, order_direction):
+    result = appointments_to_be_ordered
+    if order_direction == "asc":
+        order_direction = ""
+    else:
+        order_direction = "-"
+    if order_column == "first_name":
+        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__subject__first_name')
+    elif order_column == "last_name":
+        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__subject__last_name')
+    elif order_column == "location":
+        result = appointments_to_be_ordered.order_by(order_direction + 'location')
+    elif order_column == "flying_team":
+        result = appointments_to_be_ordered.order_by(order_direction + 'flying_team')
+    elif order_column == "post_mail_sent":
+        result = appointments_to_be_ordered.order_by(order_direction + 'post_mail_sent')
+    elif order_column == "datetime_when":
+        result = appointments_to_be_ordered.order_by(order_direction + 'datetime_when')
+    else:
+        logger.warn("Unknown sort column: " + str(order_column))
+    return result
+
+
+def get_appointments_filtered(appointments_to_be_filtered, filters):
+    result = appointments_to_be_filtered
+    for row in filters:
+        column = row[0]
+        value = row[1]
+        if column == "first_name":
+            result = result.filter(visit__subject__subject__first_name__icontains=value)
+        elif column == "last_name":
+            result = result.filter(visit__subject__subject__last_name__icontains=value)
+        elif column == "location":
+            result = result.filter(location=value)
+        elif column == "flying_team":
+            result = result.filter(flying_team=value)
+        elif column == "appointment_types":
+            result = result.filter(appointment_types=value)
+        else:
+            message = "UNKNOWN filter: "
+            if column is None:
+                message += "[None]"
+            else:
+                message += str(column)
+            logger.warn(message)
+
+    return result
+
+
 @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"))
+def appointments(request, appointment_type):
+    # 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"))
+    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)
+    order = int(request.GET.get("order[0][column]", "0"))
+    order_dir = request.GET.get("order[0][dir]", "asc")
+    order_column = request.GET.get("columns[" + str(order) + "][data]", "last_name")
 
-        if min_date is not None:
-            length = 1000000000
+    min_date = request.GET.get("start_date", None)
+    max_date = request.GET.get("end_date", None)
 
-        all_appointments = get_appointments(request, type, min_date, max_date)
+    filters = get_filters_for_data_table_request(request)
 
-        count = all_appointments.count()
+    if min_date is not None:
+        length = 1000000000
+
+    all_appointments = get_appointments(request, appointment_type, min_date, max_date)
+
+    count = all_appointments.count()
 
-        sliced_subjects = all_appointments[start:(start + length)]
+    sorted_appointments = get_appointments_order(all_appointments, order_column, order_dir)
+    filtered_appointments = get_appointments_filtered(sorted_appointments, filters)
+    sliced_appointments = filtered_appointments[start:(start + length)]
 
-        result_appointments = sliced_subjects
+    result_appointments = sliced_appointments
 
-        count_filtered = all_appointments.count()
+    count_filtered = all_appointments.count()
 
-        data = []
-        for appointment in result_appointments:
-            data.append(serialize_appointment(appointment))
+    data = []
+    for appointment in result_appointments:
+        data.append(serialize_appointment(appointment))
 
-        return JsonResponse({
-            "draw": draw,
-            "recordsTotal": count,
-            "recordsFiltered": count_filtered,
-            "data": data,
-        })
-    except:
-        logger.exception("Problem with getting appointments")
-        return e500_error(request)
+    return JsonResponse({
+        "draw": draw,
+        "recordsTotal": count,
+        "recordsFiltered": count_filtered,
+        "data": data,
+    })
 
 
 def serialize_appointment(appointment):
     subject_string = ""
-    nd_number = screening_number = phone_numbers = appointment_types = None
+    first_name = ""
+    last_name = ""
+    nd_number = screening_number = phone_numbers = appointment_type_names = None
     if appointment.visit is not None:
         title = "Visit " + str(appointment.visit.visit_number)
         study_subject = appointment.visit.subject
         subject_string = study_subject.subject.last_name + " " + study_subject.subject.first_name
+        first_name = study_subject.subject.first_name
+        last_name = study_subject.subject.last_name
         nd_number = study_subject.nd_number
         screening_number = study_subject.screening_number
         phone_numbers = ", ".join(filter(None,
                                          [study_subject.subject.phone_number, study_subject.subject.phone_number_2,
                                           study_subject.subject.phone_number_3]))
-        appointment_types = ", ".join([unicode(type) for type in appointment.appointment_types.all()])
+        appointment_type_names = ", ".join(
+            [unicode(appointment_type_codes) for appointment_type_codes in appointment.appointment_types.all()])
     else:
         title = appointment.comment
 
-    type = ", ".join([type.code for type in appointment.appointment_types.all()])
-    time = ""
-    if appointment.datetime_when is not None:
-        time = appointment.datetime_when.strftime('%Y-%m-%d %H:%M')
-    until = ""
-    if appointment.datetime_when is not None:
-        until = appointment.datetime_until().strftime('%Y-%m-%d %H:%M')
+    appointment_type_codes = ", ".join(
+        [appointment_type_codes.code for appointment_type_codes in appointment.appointment_types.all()])
+    until = serialize_datetime(appointment.datetime_until())
 
-    if appointment.flying_team is None:
-        location = unicode(appointment.location)
-    else:
-        location = unicode(appointment.location) + " (" + unicode(appointment.flying_team) + ")"
+    location = location_to_str(appointment.location)
+    flying_team = flying_team_to_str(appointment.flying_team)
 
     result = {
         "subject": subject_string,
@@ -117,14 +193,19 @@ def serialize_appointment(appointment):
         "nd_number": nd_number,
         "screening_number": screening_number,
         "phone_number": phone_numbers,
-        "appointment_types": appointment_types,
-        "type": type,
-        "datetime_when": time,
+        "appointment_type_names": appointment_type_names,
         "datetime_until": until,
         "comment": appointment.comment,
         "color": appointment.color(),
         "id": appointment.id,
+
+        "first_name": first_name,
+        "last_name": last_name,
         "location": location,
+        "flying_team": flying_team,
+        "post_mail_sent": bool_to_yes_no(appointment.post_mail_sent),
+        "datetime_when": serialize_datetime(appointment.datetime_when),
+        "appointment_types": appointment_type_codes,
         "url": reverse('web.views.appointment_edit', kwargs={'id': str(appointment.id)})
     }
     return result
diff --git a/smash/web/api_views/serialization_utils.py b/smash/web/api_views/serialization_utils.py
index e1e96dbcfc8944eeff773fbd95016f76f5ef2db1..c1e857ed889a0973ead5f9dd0a8c5e644c32527d 100644
--- a/smash/web/api_views/serialization_utils.py
+++ b/smash/web/api_views/serialization_utils.py
@@ -59,3 +59,14 @@ def add_column(result, name, field_name, column_list, param, columns_used_in_stu
             "visible": visible,
             "sortable": sortable
         })
+
+
+def get_filters_for_data_table_request(request):
+    filters = []
+    column_id = 0
+    while request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") != "unknown":
+        val = request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown")
+        if val != "":
+            filters.append([request.GET.get("columns[" + str(column_id) + "][data]"), val])
+        column_id += 1
+    return filters
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index 9d2d67b3ac12f5afcc6fa32eaca82f4e1f57a0ce..ac069955aae0681027e921742dbf110f7b85c4d5 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -6,7 +6,7 @@ from django.db.models import Q
 from django.http import JsonResponse
 
 from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str, location_to_str, add_column, \
-    serialize_date, serialize_datetime
+    serialize_date, serialize_datetime, get_filters_for_data_table_request
 from web.models import StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, Study, ContactAttempt
 from web.models.constants import SUBJECT_TYPE_CHOICES, GLOBAL_STUDY_ID
 from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
@@ -260,13 +260,7 @@ def subjects(request, type):
         order_dir = request.GET.get("order[0][dir]", "asc")
         order_column = request.GET.get("columns[" + str(order) + "][data]", "last_name")
 
-        filters = []
-        column_id = 0
-        while request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") != "unknown":
-            val = request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown")
-            if val != "":
-                filters.append([request.GET.get("columns[" + str(column_id) + "][data]"), val])
-            column_id += 1
+        filters = get_filters_for_data_table_request(request)
 
         all_subjects = get_subjects(request, type)
 
diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py
index ba2258b9f19eae217529aefc70ec98b99521deab..8a7a75b7805504a5ca33158dd9689e6c804f30d5 100644
--- a/smash/web/api_views/visit.py
+++ b/smash/web/api_views/visit.py
@@ -5,7 +5,7 @@ from django.db.models import Q
 from django.http import JsonResponse
 
 from web.api_views.serialization_utils import bool_to_yes_no, flying_team_to_str, location_to_str, add_column, \
-    serialize_date
+    serialize_date, get_filters_for_data_table_request
 from web.models import AppointmentType, Appointment
 from web.models import SubjectColumns
 from web.models import Visit, Study, VisitColumns, StudyVisitList, StudyColumns
@@ -182,13 +182,7 @@ def visits(request, visit_list_type):
     order_dir = request.GET.get("order[0][dir]", "asc")
     order_column = request.GET.get("columns[" + str(order) + "][data]", "last_name")
 
-    filters = []
-    column_id = 0
-    while request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown") != "unknown":
-        val = request.GET.get("columns[" + str(column_id) + "][search][value]", "unknown")
-        if val != "":
-            filters.append([request.GET.get("columns[" + str(column_id) + "][data]"), val])
-        column_id += 1
+    filters = get_filters_for_data_table_request(request)
 
     all_visits = get_visits(request, visit_list_type)
 
diff --git a/smash/web/migrations/0088_appointmentcolumns_appointmentlist.py b/smash/web/migrations/0088_appointmentcolumns_appointmentlist.py
new file mode 100644
index 0000000000000000000000000000000000000000..3473fdc6d0f2a643a4bfe2f4e22c8c3e089bfe85
--- /dev/null
+++ b/smash/web/migrations/0088_appointmentcolumns_appointmentlist.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-07 13:07
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0087_approaching_visit_wihout_appointment_list'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AppointmentColumns',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('flying_team', models.BooleanField(default=False, verbose_name=b'Flying team')),
+                ('worker_assigned', models.BooleanField(default=False, verbose_name=b'Worker conducting the assessment')),
+                ('appointment_types', models.BooleanField(default=True, verbose_name=b'Appointment types')),
+                ('room', models.BooleanField(default=False, verbose_name=b'Room')),
+                ('location', models.BooleanField(default=False, verbose_name=b'Location')),
+                ('comment', models.BooleanField(default=False, verbose_name=b'Comment')),
+                ('datetime_when', models.BooleanField(default=True, verbose_name=b'Comment')),
+                ('length', models.BooleanField(default=False, verbose_name=b'Appointment length')),
+                ('status', models.BooleanField(default=False, verbose_name=b'Status')),
+                ('post_mail_sent', models.BooleanField(default=False, verbose_name=b'Post mail sent')),
+            ],
+        ),
+        migrations.CreateModel(
+            name='AppointmentList',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('type', models.CharField(choices=[(b'GENERIC', b'Generic'), (b'UNFINISHED', b'Unfinished'), (b'APPROACHING', b'Approaching')], max_length=50, verbose_name=b'Type of list')),
+                ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Study')),
+                ('visible_appointment_columns', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.AppointmentColumns')),
+                ('visible_study_subject_columns', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.StudyColumns')),
+                ('visible_subject_columns', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.SubjectColumns')),
+                ('visible_visit_columns', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.VisitColumns')),
+            ],
+        ),
+    ]
diff --git a/smash/web/migrations/0089_unfinshed_appointment_list.py b/smash/web/migrations/0089_unfinshed_appointment_list.py
new file mode 100644
index 0000000000000000000000000000000000000000..dabf6f9fa77d3e3451c41225cb9dfaf64df310a5
--- /dev/null
+++ b/smash/web/migrations/0089_unfinshed_appointment_list.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-05 16:50
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+# noinspection PyUnusedLocal
+# noinspection PyPep8Naming
+def create_default_columns_for_APPOINTMENT_LIST_UNFINISHED(apps, schema_editor):
+    # We can't import the Study model directly as it may be a newer
+    # version than this migration expects. We use the historical version.
+    SubjectColumns = apps.get_model("web", "SubjectColumns")
+    subject_columns = SubjectColumns.objects.create()
+    subject_columns.sex = False
+    subject_columns.first_name = True
+    subject_columns.last_name = True
+    subject_columns.languages = False
+    subject_columns.default_written_communication_language = False
+    subject_columns.phone_number = False
+    subject_columns.phone_number_2 = False
+    subject_columns.phone_number_3 = False
+    subject_columns.email = False
+    subject_columns.date_born = False
+    subject_columns.address = False
+    subject_columns.postal_code = False
+    subject_columns.city = False
+    subject_columns.country = False
+    subject_columns.dead = False
+    subject_columns.save()
+
+    StudyColumns = apps.get_model("web", "StudyColumns")
+    study_columns = StudyColumns.objects.create()
+    study_columns.postponed = False
+    study_columns.datetime_contact_reminder = False
+    study_columns.type = False
+    study_columns.default_location = True
+    study_columns.flying_team = True
+    study_columns.screening_number = False
+    study_columns.nd_number = False
+    study_columns.mpower_id = False
+    study_columns.comments = False
+    study_columns.referral = False
+    study_columns.diagnosis = False
+    study_columns.year_of_diagnosis = False
+    study_columns.information_sent = False
+    study_columns.pd_in_family = False
+    study_columns.resigned = False
+    study_columns.resign_reason = False
+    study_columns.save()
+
+    VisitColumns = apps.get_model("web", "VisitColumns")
+    visit_columns = VisitColumns.objects.create()
+    visit_columns.datetime_begin = True
+    visit_columns.datetime_end = False
+    visit_columns.is_finished = False
+    visit_columns.post_mail_sent = False
+    visit_columns.visit_number = True
+    visit_columns.visible_appointment_types = False
+    visit_columns.save()
+
+    AppointmentColumns = apps.get_model("web", "AppointmentColumns")
+    appointment_columns = AppointmentColumns.objects.create()
+    appointment_columns.flying_team = False
+    appointment_columns.worker_assigned = False
+    appointment_columns.appointment_types = True
+    appointment_columns.room = False
+    appointment_columns.location = False
+    appointment_columns.comment = False
+    appointment_columns.datetime_when = True
+    appointment_columns.length = False
+    appointment_columns.status = False
+    appointment_columns.post_mail_sent = False
+    appointment_columns.save()
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0088_appointmentcolumns_appointmentlist'),
+    ]
+
+    operations = [
+        migrations.RunPython(create_default_columns_for_APPOINTMENT_LIST_UNFINISHED),
+        migrations.RunSQL('INSERT INTO web_appointmentlist (' +
+                          'study_id, ' +
+                          'visible_visit_columns_id, ' +
+                          'visible_subject_columns_id, ' +
+                          'visible_study_subject_columns_id, ' +
+                          'visible_appointment_columns_id, ' +
+                          'type) ' +
+                          "SELECT " +
+                          "1, " +
+                          "max(web_visitcolumns.id), " +
+                          "max(web_subjectcolumns.id), " +
+                          "max(web_studycolumns.id), " +
+                          "max(web_appointmentcolumns.id), " +
+                          "'UNFINISHED' FROM web_visitcolumns, web_studycolumns, web_subjectcolumns, web_appointmentcolumns;"),
+    ]
diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py
index 746b15bf599f4f08e2605b8274ddc97137b609d8..d9ee4dab7c6289bcac5c3daa75f84d463f9fde83 100644
--- a/smash/web/models/__init__.py
+++ b/smash/web/models/__init__.py
@@ -8,6 +8,7 @@ from flying_team import FlyingTeam
 from location import Location
 from appointment_type_link import AppointmentTypeLink
 from country import Country
+from appointment_columns import AppointmentColumns
 from subject_columns import SubjectColumns
 from study_columns import StudyColumns
 from visit_columns import VisitColumns
@@ -26,6 +27,7 @@ from subject import Subject
 from study_subject import StudySubject
 from study_subject_list import StudySubjectList
 from study_visit_list import StudyVisitList
+from appointment_list import AppointmentList
 from contact_attempt import ContactAttempt
 from mail_template import MailTemplate
 from missing_subject import MissingSubject
@@ -33,5 +35,6 @@ from inconsistent_subject import InconsistentSubject, InconsistentField
 
 __all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room,
            Subject, StudySubject, StudySubjectList, SubjectColumns, StudyNotificationParameters,
-           Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate, AppointmentTypeLink, MissingSubject,
-           InconsistentSubject, InconsistentField, Country, StudyColumns, VisitColumns, StudyVisitList]
+           AppointmentList, AppointmentColumns, Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate,
+           AppointmentTypeLink,
+           MissingSubject, InconsistentSubject, InconsistentField, Country, StudyColumns, VisitColumns, StudyVisitList]
diff --git a/smash/web/models/appointment_columns.py b/smash/web/models/appointment_columns.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a65c60b9347ed2612036e6516a33e48c0fc8c28
--- /dev/null
+++ b/smash/web/models/appointment_columns.py
@@ -0,0 +1,47 @@
+# coding=utf-8
+from django.db import models
+
+
+class AppointmentColumns(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    flying_team = models.BooleanField(default=False,
+                                      verbose_name='Flying team',
+                                      )
+
+    worker_assigned = models.BooleanField(default=False,
+                                          verbose_name='Worker conducting the assessment',
+                                          )
+
+    appointment_types = models.BooleanField(default=True,
+                                            verbose_name='Appointment types',
+                                            )
+
+    room = models.BooleanField(default=False,
+                               verbose_name='Room',
+                               )
+
+    location = models.BooleanField(default=False,
+                                   verbose_name='Location',
+                                   )
+
+    comment = models.BooleanField(default=False,
+                                  verbose_name='Comment',
+                                  )
+
+    datetime_when = models.BooleanField(default=True,
+                                        verbose_name='Comment',
+                                        )
+
+    length = models.BooleanField(default=False,
+                                 verbose_name='Appointment length',
+                                 )
+
+    status = models.BooleanField(default=False,
+                                 verbose_name='Status',
+                                 )
+
+    post_mail_sent = models.BooleanField(default=False,
+                                         verbose_name='Post mail sent',
+                                         )
diff --git a/smash/web/models/appointment_list.py b/smash/web/models/appointment_list.py
new file mode 100644
index 0000000000000000000000000000000000000000..e04142320afcd4dddbd82ab6b4cf1dbc9efcb61c
--- /dev/null
+++ b/smash/web/models/appointment_list.py
@@ -0,0 +1,52 @@
+# coding=utf-8
+from django.db import models
+
+from web.models import Study, SubjectColumns, VisitColumns, AppointmentColumns, StudyColumns
+
+APPOINTMENT_LIST_GENERIC = "GENERIC"
+APPOINTMENT_LIST_UNFINISHED = "UNFINISHED"
+APPOINTMENT_LIST_APPROACHING = "APPROACHING"
+
+APPOINTMENT_LIST_CHOICES = {
+    APPOINTMENT_LIST_GENERIC: 'Generic',
+    APPOINTMENT_LIST_UNFINISHED: 'Unfinished',
+    APPOINTMENT_LIST_APPROACHING: 'Approaching',
+}
+
+
+class AppointmentList(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    study = models.ForeignKey(
+        Study,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+
+    visible_visit_columns = models.ForeignKey(
+        VisitColumns,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+
+    visible_subject_columns = models.ForeignKey(
+        SubjectColumns,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+    visible_study_subject_columns = models.ForeignKey(
+        StudyColumns,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+    visible_appointment_columns = models.ForeignKey(
+        AppointmentColumns,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+
+    type = models.CharField(max_length=50,
+                            choices=APPOINTMENT_LIST_CHOICES.items(),
+                            verbose_name='Type of list',
+                            )
diff --git a/smash/web/static/js/appointment.js b/smash/web/static/js/appointment.js
index a4d594bab159699c5a2c247589176075e5f788ad..86ad7cd8848bb9b1752d434a6c72cd799438e141 100644
--- a/smash/web/static/js/appointment.js
+++ b/smash/web/static/js/appointment.js
@@ -208,7 +208,6 @@ function get_calendar_events_function(source, allow_url_redirection, day_headers
     }
     return function (start, end, timezone, callback) {
         if (day_headers !== undefined) {
-            console.log("XXXX");
             $.ajax({
                 data: {
                     start_date: start.format(),
@@ -245,7 +244,7 @@ function get_calendar_events_function(source, allow_url_redirection, day_headers
                     const entry = doc.data[i];
                     var title = entry.subject;
                     if (title !== "") {
-                        title += " (" + entry.nd_number + "); type: " + entry.type;
+                        title += " (" + entry.nd_number + "); type: " + entry.appointment_types;
                     } else {
                         title = entry.title
                     }
@@ -257,9 +256,11 @@ function get_calendar_events_function(source, allow_url_redirection, day_headers
                         color: entry.color,
                         nd_number: entry.nd_number,
                         location: entry.location,
+                        flying_team: entry.flying_team,
                         screening_number: entry.screening_number,
                         phone_number: entry.phone_number,
-                        appointment_types: entry.appointment_types
+                        appointment_types: entry.appointment_types,
+                        appointment_type_names: entry.appointment_type_names
                     };
                     if (allow_url_redirection) {
                         event["url"] = entry.url;
@@ -271,3 +272,7 @@ function get_calendar_events_function(source, allow_url_redirection, day_headers
         });
     }
 }
+
+function createAppointmentsTable(params) {
+    return createTable(params);
+}
diff --git a/smash/web/templates/appointments/index.html b/smash/web/templates/appointments/index.html
index 81a642dc496a82ee4af2cf402e88b9ce640c42c2..0452729eb4b61a1b8c3d2942299ac2040b637240 100644
--- a/smash/web/templates/appointments/index.html
+++ b/smash/web/templates/appointments/index.html
@@ -133,11 +133,15 @@
                     if (event.nd_number) {
                         content += '<li>ND number: ' + event.nd_number + '</li>'
                     }
-                    if (event.appointment_types) {
-                        content += '<li>Appointment types: ' + event.appointment_types + '</li>'
+                    if (event.appointment_type_names) {
+                        content += '<li>Appointment types: ' + event.appointment_type_names + '</li>'
                     }
                     if (event.location) {
-                        content += '<li>Location: ' + event.location + '</li>'
+                        var location = event.location;
+                        if (event.flying_team) {
+                            location += " (" + event.flying_team + ")";
+                        }
+                        content += '<li>Location: ' + location + '</li>'
                     }
                     content += "</ul>";
                     $(element).popover({
diff --git a/smash/web/templates/appointments/list.html b/smash/web/templates/appointments/list.html
index b20d99c7b0dfcf3dee0d1eb9609ae3902b84faf0..24de957c72f74339e13d3d31e08ca4acdd50ce42 100644
--- a/smash/web/templates/appointments/list.html
+++ b/smash/web/templates/appointments/list.html
@@ -19,25 +19,12 @@
 {% endblock breadcrumb %}
 
 {% block maincontent %}
-    <div class="row">
-        <div class="col-md-16">
-            <table id="table" class="table table-bordered table-striped">
-                <thead>
-                <tr>
-                    <th>Subject</th>
-                    <th>Visit</th>
-                    <th>Type</th>
-                    <th>Date</th>
-                    <th>Details</th>
-                    <th>Edit</th>
-                </tr>
-                </thead>
-                <tbody>
-                </tbody>
-                <tfoot style="display: table-header-group;"/>
-            </table>
-
-        </div>
+    <div class="box-body">
+        <table id="table" class="table table-bordered table-striped table-responsive">
+        </table>
+    </div>
+    <h3>Visible columns</h3>
+    <div id="visible-column-checkboxes" style="display:table; width:100%">
     </div>
 {% endblock maincontent %}
 
@@ -46,53 +33,32 @@
 
     <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
     <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
+    <script src="{% static 'js/appointment.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#}
-{#                    }#}
-                ]
-            });
+        function getSubjectEditUrl(id) {
+            return "{% url 'web.views.appointment_edit' 1234567 %}".replace(/1234567/, id);
+        }
 
-            $('#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;
-            });
+        var worker_locations = [];
+        {% for location in worker.locations.all %}
+            worker_locations.push({id: location.id, name: location.name});
+        {% endfor %}
 
-            $('#table_filter').css("display", "none");
+        $.get("{% url 'web.api.appointments.columns' list_type %}", function (data) {
+            createAppointmentsTable({
+                worker_locations: worker_locations,
+                appointment_types_url: "{% url 'web.api.appointment_types' %}",
+                subject_types_url: "{% url 'web.api.subject_types' %}",
+                locations_url: "{% url 'web.api.locations' %}",
+                subjects_url: "{% url 'web.api.appointments' list_type %}",
+                flying_teams_url: "{% url 'web.api.flying_teams' %}",
+                tableElement: document.getElementById("table"),
+                columns: getColumns(data.columns, getSubjectEditUrl),
+                checkboxesElement: document.getElementById("visible-column-checkboxes")
+            })
         });
+
     </script>
 
 {% endblock scripts %}
diff --git a/smash/web/tests/api_views/test_appointment.py b/smash/web/tests/api_views/test_appointment.py
index 5074b3ddb061aa046631ae32b2b6b8f8135c23ab..6e9bed327b4708bf3ab160b5bcfa097becc0c287 100644
--- a/smash/web/tests/api_views/test_appointment.py
+++ b/smash/web/tests/api_views/test_appointment.py
@@ -1,33 +1,23 @@
 # coding=utf-8
 
 import datetime
+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.models import AppointmentTypeLink
-from web.tests.functions import create_study_subject, create_worker, create_visit, create_appointment, \
-    create_appointment_type, create_get_suffix, create_flying_team
+from web.api_views.appointment import get_appointments_filtered, get_appointments_order
+from web.models import AppointmentTypeLink, AppointmentList, Appointment
+from web.tests import LoggedInWithWorkerTestCase
+from web.tests.functions import create_study_subject, create_visit, create_appointment, \
+    create_appointment_type, create_get_suffix, create_flying_team, create_location
 from web.views.appointment import APPOINTMENT_LIST_GENERIC, APPOINTMENT_LIST_APPROACHING, APPOINTMENT_LIST_UNFINISHED
 from web.views.notifications import get_today_midnight_date
 
 
-class TestAppointmentApi(TestCase):
+class TestAppointmentApi(LoggedInWithWorkerTestCase):
     def setUp(self):
+        super(TestAppointmentApi, self).setUp()
         self.study_subject = create_study_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):
         place = "Some new flying team location"
@@ -45,7 +35,7 @@ class TestAppointmentApi(TestCase):
         appointment2.save()
         AppointmentTypeLink.objects.create(appointment=appointment2, appointment_type=appointment_type)
 
-        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_GENERIC})
+        url = reverse('web.api.appointments', kwargs={'appointment_type': APPOINTMENT_LIST_GENERIC})
         response = self.client.get(url)
 
         self.assertEqual(response.status_code, 200)
@@ -59,7 +49,7 @@ class TestAppointmentApi(TestCase):
         appointment.flying_team = create_flying_team(place=place)
         appointment.save()
 
-        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_GENERIC})
+        url = reverse('web.api.appointments', kwargs={'appointment_type': APPOINTMENT_LIST_GENERIC})
         response = self.client.get(url)
 
         self.assertEqual(response.status_code, 200)
@@ -72,7 +62,7 @@ class TestAppointmentApi(TestCase):
         visit = create_visit(self.study_subject)
         create_appointment(visit, get_today_midnight_date() + datetime.timedelta(days=2))
 
-        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_APPROACHING})
+        url = reverse('web.api.appointments', kwargs={'appointment_type': APPOINTMENT_LIST_APPROACHING})
         response = self.client.get(url)
 
         self.assertEqual(response.status_code, 200)
@@ -85,7 +75,7 @@ class TestAppointmentApi(TestCase):
         visit = create_visit(self.study_subject)
         create_appointment(visit, get_today_midnight_date() + datetime.timedelta(days=-12))
 
-        url = reverse('web.api.appointments', kwargs={'type': APPOINTMENT_LIST_UNFINISHED})
+        url = reverse('web.api.appointments', kwargs={'appointment_type': APPOINTMENT_LIST_UNFINISHED})
         response = self.client.get(url)
 
         self.assertEqual(response.status_code, 200)
@@ -106,7 +96,7 @@ class TestAppointmentApi(TestCase):
             "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})
+                                                           kwargs={'appointment_type': APPOINTMENT_LIST_GENERIC})
         response = self.client.get(url)
 
         self.assertEqual(response.status_code, 200)
@@ -114,8 +104,187 @@ class TestAppointmentApi(TestCase):
 
         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})
+                                                           kwargs={'appointment_type': APPOINTMENT_LIST_GENERIC})
         response = self.client.get(url)
 
         self.assertEqual(response.status_code, 200)
         self.assertTrue(name in response.content)
+
+    def test_get_columns(self):
+        response = self.client.get(
+            reverse('web.api.appointments.columns', kwargs={'appointment_list_type': APPOINTMENT_LIST_GENERIC}))
+        self.assertEqual(response.status_code, 200)
+
+        columns = json.loads(response.content)['columns']
+        visible_columns = 0
+        for column in columns:
+            if column["visible"]:
+                visible_columns += 1
+        self.assertTrue(visible_columns > 0)
+
+    def test_get_columns_for_require_contact(self):
+        response = self.client.get(
+            reverse('web.api.appointments.columns', kwargs={'appointment_list_type': APPOINTMENT_LIST_UNFINISHED}))
+        self.assertEqual(response.status_code, 200)
+
+        columns = json.loads(response.content)['columns']
+        visible_columns = 0
+        for column in columns:
+            if column["visible"]:
+                visible_columns += 1
+        self.assertTrue(visible_columns > 0)
+
+    def test_get_columns_when_no_list_is_available(self):
+        AppointmentList.objects.all().delete()
+        response = self.client.get(
+            reverse('web.api.appointments.columns', kwargs={'appointment_list_type': APPOINTMENT_LIST_GENERIC}))
+        self.assertEqual(response.status_code, 200)
+
+        columns = json.loads(response.content)['columns']
+        self.assertTrue(len(columns) > 0)
+
+    def check_appointment_filtered(self, filters, result):
+        appointments = get_appointments_filtered(Appointment.objects.all(), filters)
+        self.assertEqual(len(result), appointments.count())
+        for index in range(len(result)):
+            self.assertEqual(result[index], appointments[index])
+
+    def check_appointment_ordered(self, order, result):
+        appointments = get_appointments_order(Appointment.objects.all(), order, "asc")
+        self.assertEqual(len(result), appointments.count())
+        for index in range(len(result)):
+            self.assertEqual(result[index], appointments[index])
+
+        appointments = get_appointments_order(Appointment.objects.all(), order, "desc")
+        length = len(result)
+        self.assertEqual(length, appointments.count())
+        for index in range(length):
+            self.assertEqual(result[length - index - 1], appointments[index])
+
+    def test_appointment_sort_first_name(self):
+        subject = self.study_subject
+        subject.subject.first_name = "PPP"
+        subject.subject.save()
+
+        subject2 = create_study_subject(2)
+        subject2.subject.first_name = "QQQ"
+        subject2.subject.save()
+
+        appointment = create_appointment(create_visit(subject))
+        appointment2 = create_appointment(create_visit(subject2))
+
+        self.check_appointment_ordered("first_name", [appointment, appointment2])
+
+    def test_appointment_sort_last_name(self):
+        subject = self.study_subject
+        subject.subject.last_name = "PPP"
+        subject.subject.save()
+
+        subject2 = create_study_subject(2)
+        subject2.subject.last_name = "QQQ"
+        subject2.subject.save()
+
+        appointment = create_appointment(create_visit(subject))
+        appointment2 = create_appointment(create_visit(subject2))
+
+        self.check_appointment_ordered("last_name", [appointment, appointment2])
+
+    def test_appointment_sort_default_location(self):
+        subject = self.study_subject
+        subject2 = create_study_subject(2)
+
+        appointment = create_appointment(create_visit(subject))
+        appointment.location = create_location(name="x")
+        appointment.save()
+        appointment2 = create_appointment(create_visit(subject2))
+        appointment2.location = create_location(name="y")
+        appointment2.save()
+
+        self.check_appointment_ordered("location", [appointment, appointment2])
+
+    def test_appointment_sort_flying_team(self):
+        subject = self.study_subject
+        subject2 = create_study_subject(2)
+
+        appointment = create_appointment(create_visit(subject))
+        appointment.flying_team = create_flying_team(place="x")
+        appointment.save()
+        appointment2 = create_appointment(create_visit(subject2))
+        appointment2.flying_team = create_flying_team(place="y")
+        appointment2.save()
+
+        self.check_appointment_ordered("flying_team", [appointment, appointment2])
+
+    def test_appointment_sort_post_mail_sent(self):
+        appointment = create_appointment(create_visit(self.study_subject))
+
+        self.check_appointment_ordered("post_mail_sent", [appointment])
+
+    def test_appointment_sort_datetime_when(self):
+        subject = self.study_subject
+        subject2 = create_study_subject(2)
+
+        appointment = create_appointment(create_visit(subject))
+        appointment.datetime_when = "2017-10-10"
+        appointment.save()
+        appointment2 = create_appointment(create_visit(subject2))
+        appointment2.datetime_when = "2017-10-12"
+        appointment2.save()
+
+        self.check_appointment_ordered("datetime_when", [appointment, appointment2])
+
+    def test_appointment_sort_unknown(self):
+        appointment = create_appointment(create_visit(self.study_subject))
+
+        self.check_appointment_ordered("some_unknown", [appointment])
+        self.check_appointment_ordered("", [appointment])
+        self.check_appointment_ordered(None, [appointment])
+
+    def test_subjects_filter_first_name(self):
+        subject = self.study_subject.subject
+        subject.first_name = "QQ"
+        subject.save()
+
+        appointment = create_appointment(create_visit(self.study_subject))
+
+        self.check_appointment_filtered([["first_name", "QQ"]], [appointment])
+        self.check_appointment_filtered([["first_name", "PP"]], [])
+
+    def test_subjects_filter_last_name(self):
+        subject = self.study_subject.subject
+        subject.last_name = "QQ"
+        subject.save()
+
+        appointment = create_appointment(create_visit(self.study_subject))
+
+        self.check_appointment_filtered([["last_name", "QQ"]], [appointment])
+        self.check_appointment_filtered([["last_name", "PP"]], [])
+
+    def test_subjects_filter_location(self):
+        appointment = create_appointment(create_visit(self.study_subject))
+
+        self.check_appointment_filtered([["location", str(appointment.location.id)]], [appointment])
+        self.check_appointment_filtered([["location", "-1"]], [])
+
+    def test_subjects_filter_flying_team(self):
+        appointment = create_appointment(create_visit(self.study_subject))
+        appointment.flying_team = create_flying_team()
+        appointment.save()
+
+        self.check_appointment_filtered([["flying_team", str(appointment.flying_team.id)]], [appointment])
+        self.check_appointment_filtered([["flying_team", "-1"]], [])
+
+    def test_subjects_filter_appointment_types(self):
+        appointment = create_appointment(create_visit(self.study_subject))
+        appointment_type = create_appointment_type()
+        AppointmentTypeLink.objects.create(appointment_type=appointment_type, appointment=appointment)
+
+        self.check_appointment_filtered([["appointment_types", str(appointment_type.id)]], [appointment])
+        self.check_appointment_filtered([["appointment_types", "-1"]], [])
+
+    def test_appointment_filter_unknown(self):
+        appointment = create_appointment(create_visit(self.study_subject))
+
+        self.check_appointment_filtered([["some_unknown", "unk"]], [appointment])
+        self.check_appointment_filtered([["", ""]], [appointment])
+        self.check_appointment_filtered([[None, None]], [appointment])
diff --git a/smash/web/tests/view/test_notifications.py b/smash/web/tests/view/test_notifications.py
index 677ce30bec0e8d5481fcba8972406ec861b06ee9..e02b515089640176d13e3b57c13c0b7a902a64b8 100644
--- a/smash/web/tests/view/test_notifications.py
+++ b/smash/web/tests/view/test_notifications.py
@@ -286,6 +286,16 @@ class NotificationViewTests(LoggedInTestCase):
         notification = get_unfinished_appointments_count(self.user)
         self.assertEquals(original_notification.count + 1, notification.count)
 
+    def test_get_unfinished_appointments_count_for_general_appointments(self):
+        appointment = create_appointment()
+        appointment.visit = None
+        appointment.datetime_when = "2011-01-01"
+        appointment.status = Appointment.APPOINTMENT_STATUS_SCHEDULED
+        appointment.save()
+
+        notification = get_unfinished_appointments_count(self.user)
+        self.assertEquals(0, notification.count)
+
     def test_get_unfinished_appointments_count_2(self):
         original_notification = get_unfinished_appointments_count(self.user)
         subject = create_study_subject()
diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py
index ff4afa90d3a537f54d1eb140df02a61de0faccda..6d64acd468487ffc1a569241661c309d5b12f5ae 100644
--- a/smash/web/views/appointment.py
+++ b/smash/web/views/appointment.py
@@ -6,15 +6,13 @@ from django.contrib import messages
 from django.core.exceptions import ValidationError
 from django.shortcuts import get_object_or_404, redirect
 
+from web.models.appointment_list import APPOINTMENT_LIST_APPROACHING, APPOINTMENT_LIST_GENERIC, \
+    APPOINTMENT_LIST_UNFINISHED
 from . import wrap_response
 from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, SubjectEditForm, \
     StudySubjectEditForm
 from ..models import Appointment, StudySubject, MailTemplate
 
-APPOINTMENT_LIST_GENERIC = "GENERIC"
-APPOINTMENT_LIST_UNFINISHED = "UNFINISHED"
-APPOINTMENT_LIST_APPROACHING = "APPROACHING"
-
 logger = logging.getLogger(__name__)
 
 
diff --git a/smash/web/views/notifications.py b/smash/web/views/notifications.py
index 4d251909544bc61cacaade3a0a309e9b4caa39b8..2ef58548948192fb1b4fb4d606858c8eba81a85c 100644
--- a/smash/web/views/notifications.py
+++ b/smash/web/views/notifications.py
@@ -235,7 +235,7 @@ def get_unfinished_appointments(user):
         datetime_when__lt=get_today_midnight_date(),
         status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
         location__in=get_filter_locations(user),
-    ).order_by('datetime_when')
+    ).exclude(visit__isnull=True).order_by('datetime_when')
 
 
 def waiting_for_appointment(visit):