diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py
index 98c169a9e6563d9858910d3e17b00fa6422ba798..4adab0a0359062730e5d801833ce0a2ddae4fe3d 100644
--- a/smash/web/tests/functions.py
+++ b/smash/web/tests/functions.py
@@ -4,7 +4,7 @@ from django.contrib.auth.models import User
 
 from web.models import Location, AppointmentType, Subject, Worker, Visit, Appointment
 from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL
-from web.views import get_today_midnight_date
+from web.views.notifications import get_today_midnight_date
 
 
 def create_location(name="test"):
diff --git a/smash/web/tests/test_view_functions.py b/smash/web/tests/test_view_functions.py
index b43d3c33b6064ce5b4d29bca4b49c2e07ae3240a..db206b6b31194cdfbe5a955e6973d872a0edf87f 100644
--- a/smash/web/tests/test_view_functions.py
+++ b/smash/web/tests/test_view_functions.py
@@ -1,7 +1,7 @@
 from django.test import TestCase
 
 from functions import create_location, create_user, create_worker, get_test_location
-from web.views import get_filter_locations
+from web.views.notifications import get_filter_locations
 
 
 class ViewFunctionsTests(TestCase):
diff --git a/smash/web/tests/test_view_kit_request.py b/smash/web/tests/test_view_kit_request.py
index 19883898505928742ed2d051fb1bef542826ae10..556308fd0066b8449868cf91215fc6ca154fba10 100644
--- a/smash/web/tests/test_view_kit_request.py
+++ b/smash/web/tests/test_view_kit_request.py
@@ -3,9 +3,10 @@ import datetime
 from django.test import TestCase, RequestFactory
 from django.urls import reverse
 
-from functions import create_user, create_appointment_type, create_appointment, create_location
+from functions import create_user, create_appointment_type, create_appointment
 from web.models import Item, Appointment
-from web.views import kit_requests, get_today_midnight_date
+from web.views.kit import kit_requests
+from web.views.notifications import get_today_midnight_date
 
 
 class ViewFunctionsTests(TestCase):
diff --git a/smash/web/tests/test_view_notifications.py b/smash/web/tests/test_view_notifications.py
index 60095c78547145cc621cfd586fc0233db88a60b5..cf193787194089fdb78612a2ef2c5962bb97aa44 100644
--- a/smash/web/tests/test_view_notifications.py
+++ b/smash/web/tests/test_view_notifications.py
@@ -7,10 +7,11 @@ from functions import create_appointment, create_location, create_worker, create
 from functions import create_subject
 from functions import create_visit
 from web.models import Appointment, Location
-from web.views import get_exceeded_visit_notifications_count, get_approaching_visits_without_appointments_count, \
-    get_today_midnight_date, get_subject_with_no_visit_notifications_count, get_unfinished_appointments_count, \
+from web.views.notifications import get_exceeded_visit_notifications_count, \
+    get_approaching_visits_without_appointments_count, \
+    get_subject_with_no_visit_notifications_count, get_unfinished_appointments_count, \
     get_approaching_visits_for_mail_contact_count, get_subjects_with_reminder_count, \
-    get_visits_without_appointments_count
+    get_visits_without_appointments_count, get_today_midnight_date
 
 
 class NotificationViewTests(TestCase):
diff --git a/smash/web/tests/test_view_visit.py b/smash/web/tests/test_view_visit.py
index da9d39fccd4ed4be075fd468ff2c4e3ff9f2a21c..e1979cea43307c6793cc2685ee0f99146170264b 100644
--- a/smash/web/tests/test_view_visit.py
+++ b/smash/web/tests/test_view_visit.py
@@ -3,7 +3,7 @@ from django.test import TestCase, RequestFactory
 from django.urls import reverse
 
 from functions import create_subject, create_visit, create_appointment
-from web.views import visit_details
+from web.views.visit import visit_details
 
 
 class VisitViewTests(TestCase):
diff --git a/smash/web/urls.py b/smash/web/urls.py
index 6cbc1fcefde1c3ad82ea887069b1d56693b3dee1..0d0d0b6356aa8491e0119076ce6637bde3693adf 100644
--- a/smash/web/urls.py
+++ b/smash/web/urls.py
@@ -20,64 +20,68 @@ from django.conf.urls import url
 from web import views
 
 urlpatterns = [
-    url(r'^appointments$', views.appointments, name='web.views.appointments'),
-    url(r'^appointments/unfinished$', views.unfinished_appointments, name='web.views.unfinished_appointments'),
-    url(r'^appointments/details/(?P<id>\d+)$', views.appointment_details, name='web.views.appointment_details'),
-    url(r'^appointments/add/(?P<id>\d+)$', views.appointment_add, name='web.views.appointment_add'),
-    url(r'^appointments/edit/(?P<id>\d+)$', views.appointment_edit, name='web.views.appointment_edit'),
-    url(r'^appointments/edit_datetime/(?P<id>\d+)$', views.appointment_edit_datetime,
+    url(r'^appointments$', views.appointment.appointments, name='web.views.appointments'),
+    url(r'^appointments/unfinished$', views.appointment.unfinished_appointments,
+        name='web.views.unfinished_appointments'),
+    url(r'^appointments/details/(?P<id>\d+)$', views.appointment.appointment_details,
+        name='web.views.appointment_details'),
+    url(r'^appointments/add/(?P<id>\d+)$', views.appointment.appointment_add, name='web.views.appointment_add'),
+    url(r'^appointments/edit/(?P<id>\d+)$', views.appointment.appointment_edit, name='web.views.appointment_edit'),
+    url(r'^appointments/edit_datetime/(?P<id>\d+)$', views.appointment.appointment_edit_datetime,
         name='web.views.appointment_edit_datetime'),
-    url(r'^appointments/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.appointment_mark,
+    url(r'^appointments/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.appointment.appointment_mark,
         name='web.views.appointment_mark'),
 
-    url(r'^visits$', views.visits, name='web.views.visits'),
-    url(r'^visits/exceeded$', views.exceeded_visits, name='web.views.exceeded_visits'),
-    url(r'^visits/unfinished$', views.unfinished_visits, name='web.views.unfinished_visits'),
-    url(r'^visits/approaching$', views.approaching_visits_without_appointments,
+    url(r'^visits$', views.visit.visits, name='web.views.visits'),
+    url(r'^visits/exceeded$', views.visit.exceeded_visits, name='web.views.exceeded_visits'),
+    url(r'^visits/unfinished$', views.visit.unfinished_visits, name='web.views.unfinished_visits'),
+    url(r'^visits/approaching$', views.visit.approaching_visits_without_appointments,
         name='web.views.approaching_visits_without_appointments'),
-    url(r'^visits/approaching_post_mail$', views.approaching_visits_for_mail_contact,
+    url(r'^visits/approaching_post_mail$', views.visit.approaching_visits_for_mail_contact,
         name='web.views.approaching_visits_for_mail_contact'),
-    url(r'^visits/missing_appointments$', views.visits_with_missing_appointments,
+    url(r'^visits/missing_appointments$', views.visit.visits_with_missing_appointments,
         name='web.views.visits_with_missing_appointments'),
-    url(r'^visits/details/(?P<id>\d+)$', views.visit_details, name='web.views.visit_details'),
-    url(r'^visits/add$', views.visit_add, name='web.views.visit_add'),
-    url(r'^visits/add/(?P<subject_id>\d+)$', views.visit_add, name='web.views.visit_add'),
-    url(r'^visit/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.visit_mark, name='web.views.visit_mark'),
+    url(r'^visits/details/(?P<id>\d+)$', views.visit.visit_details, name='web.views.visit_details'),
+    url(r'^visits/add$', views.visit.visit_add, name='web.views.visit_add'),
+    url(r'^visits/add/(?P<subject_id>\d+)$', views.visit.visit_add, name='web.views.visit_add'),
+    url(r'^visit/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.visit.visit_mark, name='web.views.visit_mark'),
 
-    url(r'^subjects$', views.subjects, name='web.views.subjects'),
-    url(r'^subjects/no_visit$', views.subject_no_visits, name='web.views.subject_no_visits'),
-    url(r'^subjects/equire_contact$', views.subject_require_contact, name='web.views.subject_require_contact'),
-    url(r'^subjects/add$', views.subject_add, name='web.views.subject_add'),
-    url(r'^subjects/subject_visit_details/(?P<id>\d+)$', views.subject_visit_details,
+    url(r'^subjects$', views.subject.subjects, name='web.views.subjects'),
+    url(r'^subjects/no_visit$', views.subject.subject_no_visits, name='web.views.subject_no_visits'),
+    url(r'^subjects/equire_contact$', views.subject.subject_require_contact, name='web.views.subject_require_contact'),
+    url(r'^subjects/add$', views.subject.subject_add, name='web.views.subject_add'),
+    url(r'^subjects/subject_visit_details/(?P<id>\d+)$', views.subject.subject_visit_details,
         name='web.views.subject_visit_details'),
-    url(r'^subjects/edit/(?P<id>\d+)$', views.subject_edit, name='web.views.subject_edit'),
-    url(r'^subjects/delete/(?P<id>\d+)$', views.subject_delete, name='web.views.subject_delete'),
-    url(r'^subjects/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.subject_mark, name='web.views.subject_mark'),
+    url(r'^subjects/edit/(?P<id>\d+)$', views.subject.subject_edit, name='web.views.subject_edit'),
+    url(r'^subjects/delete/(?P<id>\d+)$', views.subject.subject_delete, name='web.views.subject_delete'),
+    url(r'^subjects/mark/(?P<id>\d+)/(?P<as_what>[A-z]+)$', views.subject.subject_mark, name='web.views.subject_mark'),
 
-    url(r'^doctors$', views.doctors, name='web.views.doctors'),
-    url(r'^doctors/add$', views.doctor_add, name='web.views.doctor_add'),
-    url(r'^doctors/details/(?P<doctor_id>\d+)$', views.doctor_details, name='web.views.doctor_details'),
-    url(r'^doctors/edit/(?P<doctor_id>\d+)$', views.doctor_edit, name='web.views.doctor_edit'),
-    url(r'^doctors/availability/(?P<doctor_id>\d+)$', views.doctor_availability, name='web.views.doctor_availability'),
-    url(r'^doctors/availability/(?P<doctor_id>\d+)/delete/(?P<availability_id>\d+)$', views.doctor_availability_delete,
+    url(r'^doctors$', views.doctor.doctors, name='web.views.doctors'),
+    url(r'^doctors/add$', views.doctor.doctor_add, name='web.views.doctor_add'),
+    url(r'^doctors/details/(?P<doctor_id>\d+)$', views.doctor.doctor_details, name='web.views.doctor_details'),
+    url(r'^doctors/edit/(?P<doctor_id>\d+)$', views.doctor.doctor_edit, name='web.views.doctor_edit'),
+    url(r'^doctors/availability/(?P<doctor_id>\d+)$', views.doctor.doctor_availability,
+        name='web.views.doctor_availability'),
+    url(r'^doctors/availability/(?P<doctor_id>\d+)/delete/(?P<availability_id>\d+)$',
+        views.doctor.doctor_availability_delete,
         name='web.views.doctor_availability_delete'),
 
-    url(r'^equipment_and_rooms$', views.equipment_and_rooms, name='web.views.equipment_and_rooms'),
-    url(r'^equipment_and_rooms/eqdef$', views.equipment_def, name='web.views.equipment_def'),
-    url(r'^equipment_and_rooms/kit_requests$', views.kit_requests, name='web.views.kit_requests'),
-    url(r'^equipment_and_rooms/kit_requests/(?P<start_date>[\w-]+)/$', views.kit_requests_send_mail,
+    url(r'^equipment_and_rooms$', views.equipment.equipment_and_rooms, name='web.views.equipment_and_rooms'),
+    url(r'^equipment_and_rooms/eqdef$', views.equipment.equipment_def, name='web.views.equipment_def'),
+    url(r'^equipment_and_rooms/kit_requests$', views.kit.kit_requests, name='web.views.kit_requests'),
+    url(r'^equipment_and_rooms/kit_requests/(?P<start_date>[\w-]+)/$', views.kit.kit_requests_send_mail,
         name='web.views.kit_requests_send_mail'),
     url(r'^equipment_and_rooms/kit_requests/(?P<start_date>[\w-]+)/(?P<end_date>[\w-]+)/$',
-        views.kit_requests_send_mail, name='web.views.kit_requests_send_mail'),
+        views.kit.kit_requests_send_mail, name='web.views.kit_requests_send_mail'),
 
-    url(r'^mail_templates$', views.mail_templates, name='web.views.mail_templates'),
-    url(r'^statistics$', views.statistics, name='web.views.statistics'),
+    url(r'^mail_templates$', views.mails.mail_templates, name='web.views.mail_templates'),
+    url(r'^statistics$', views.statistics.statistics, name='web.views.statistics'),
 
-    url(r'^export$', views.export, name='web.views.export'),
-    url(r'^export/(?P<type>[A-z]+)$', views.export_to_csv2, name='web.views.export_to_csv2'),
+    url(r'^export$', views.export.export, name='web.views.export'),
+    url(r'^export/(?P<type>[A-z]+)$', views.export.export_to_csv2, name='web.views.export_to_csv2'),
 
-    url(r'^login$', views.login, name='web.views.login'),
-    url(r'^logout$', views.logout, name='web.views.logout'),
+    url(r'^login$', views.auth.login, name='web.views.login'),
+    url(r'^logout$', views.auth.logout, name='web.views.logout'),
 
     url(r'^$', views.index, name='web.views.index')
 ]
diff --git a/smash/web/views.py b/smash/web/views.py
deleted file mode 100644
index 989dd33c6190687c41bd808cadc8a6ae00fdd7be..0000000000000000000000000000000000000000
--- a/smash/web/views.py
+++ /dev/null
@@ -1,890 +0,0 @@
-from __future__ import unicode_literals
-
-import csv
-import datetime
-
-from django.contrib.auth.decorators import login_required
-from django.contrib.auth.models import User, AnonymousUser
-from django.db.models import Case
-from django.db.models import Count
-from django.db.models import When
-from django.forms import HiddenInput
-from django.http import HttpResponse
-from django.shortcuts import redirect, get_object_or_404
-from django.shortcuts import render
-from django.utils.dateparse import parse_datetime
-
-from auth import do_logout, do_login
-from forms import SubjectDetailForm, WorkerEditForm, WorkerDetailForm, AppointmentDetailForm, AppointmentAddForm, \
-    AppointmentEditForm, KitRequestForm, SubjectEditForm, SubjectAddForm, VisitAddForm, WorkerAddForm, VisitDetailForm, \
-    StatisticsForm
-from models import Worker, Location, Visit, Subject, Appointment, Avaibility, Item, AppointmentType
-from statistics import StatisticsManager
-from statistics import get_previous_year_and_month
-
-handler404 = 'web.views.e404_page_not_found'
-handler500 = 'web.views.e500_error'
-handler403 = 'web.views.e403_permission_denied'
-handler400 = 'web.views.e400_bad_request'
-
-
-def index(request):
-    if request.user.is_authenticated():
-        return redirect(appointments)
-    return redirect(login)
-
-
-def e404_page_not_found(request, context=None):
-    return render(request, "errors/404.html", context, status=404)
-
-
-def e500_error(request, context=None):
-    return render(request, "errors/500.html", context, status=500)
-
-
-def e403_permission_denied(request, context=None):
-    return render(request, "errors/403.html", context, status=403)
-
-
-def e400_bad_request(request, context=None):
-    return render(request, "errors/400.html", context, status=400)
-
-
-def login(request):
-    context = {
-        'state': 'initial'
-    }
-    if request.GET and request.GET.get('error'):
-        context['state'] = request.GET.get('error')
-
-    if request.method == "GET" and request.GET:
-        context['next'] = request.GET.get('next')
-
-    if request.method == "POST" and request.POST:
-        state, message = do_login(request)
-        if state:
-            if request.POST.get('next'):
-                return redirect(request.POST.get('next'))
-            else:
-                return redirect(appointments)
-        else:
-            return redirect('/login?error=' + message)
-    return render(request, "login.html", context)
-
-
-class NotificationCount(object):
-    title = ""
-    count = 0
-    style = ""
-    type = ''
-
-    def __init__(self, title="Unknown", count=0, style="fa fa-users text-aqua", type='web.views.appointments'):
-        self.title = title
-        self.count = count
-        self.style = style
-        self.type = type
-
-
-def get_filter_locations(user):
-    worker = None
-
-    if isinstance(user, User):
-        workers = Worker.objects.filter(user=user)
-        if len(workers) > 0:
-            worker = workers[0]
-    elif isinstance(user, Worker):
-        worker = user
-    elif isinstance(user, AnonymousUser):
-        # anonymous user shouldn't see anything
-        return Location.objects.filter(id=-1)
-    elif user is not None:
-        raise TypeError("Unknown class type: " + user.__class__.__name__)
-
-    if worker is None or worker.locations.count() == 0:
-        return Location.objects.all()
-    else:
-        return worker.locations.all()
-
-
-def get_exceeded_visits(user):
-    return Visit.objects.filter(datetime_end__lt=get_today_midnight_date(),
-                                is_finished=False,
-                                subject__default_location__in=get_filter_locations(user)
-                                ).order_by('datetime_begin')
-
-
-def get_exceeded_visit_notifications_count(user):
-    notification = NotificationCount(
-        title="exceeded visit time",
-        count=get_exceeded_visits(user).count(),
-        style="fa fa-thermometer-4 text-red",
-        type='web.views.exceeded_visits')
-    return notification
-
-
-def get_subjects_with_no_visit(user):
-    result = Subject.objects.annotate(my_count=Count(Case(When(visit__is_finished=False, then=1)))).filter(
-        dead=False,
-        resigned=False,
-        my_count=0,
-        default_location__in=get_filter_locations(user),
-        postponed=False,
-        datetime_contact_reminder__isnull=True,
-    )
-    return result
-
-
-def get_subjects_with_reminder(user):
-    tomorrow = get_today_midnight_date() + datetime.timedelta(days=1)
-
-    result = Subject.objects.filter(
-        dead=False,
-        resigned=False,
-        default_location__in=get_filter_locations(user),
-        datetime_contact_reminder__lt=tomorrow,
-    )
-    return result
-
-
-def get_subjects_with_reminder_count(user):
-    notification = NotificationCount(
-        title="subject required contact",
-        count=get_subjects_with_reminder(user).count(),
-        style="fa fa-users text-aqua",
-        type='web.views.subject_require_contact')
-    return notification
-
-
-def get_subject_with_no_visit_notifications_count(user):
-    notification = NotificationCount(
-        title="subject without visit",
-        count=get_subjects_with_no_visit(user).count(),
-        style="fa fa-users text-aqua",
-        type='web.views.subject_no_visits')
-    return notification
-
-
-def get_visits_without_appointments_count(user):
-    notification = NotificationCount(
-        title="unfinished visits ",
-        count=len(get_unfinished_visits(user)),
-        style="fa fa-user-times text-yellow",
-        type='web.views.unfinished_visits')
-    return notification
-
-
-def get_visits_with_missing_appointments_count(user):
-    notification = NotificationCount(
-        title="visits with missing appointments",
-        count=len(get_active_visits_with_missing_appointments(user)),
-        style="fa fa-user-times text-yellow",
-        type='web.views.visits_with_missing_appointments')
-    return notification
-
-
-def get_active_visits_without_appointments(user):
-    today = get_today_midnight_date()
-    return Visit.objects.annotate(
-        my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter(
-        datetime_begin__lt=today,
-        datetime_end__gt=today,
-        is_finished=False,
-        subject__default_location__in=get_filter_locations(user),
-        my_count=0)
-
-
-def waiting_for_appointment(visit):
-    required_types = visit.appointment_types.all()
-    appointment_types = []
-    for appointment in visit.appointment_set.all():
-        for type in appointment.appointment_types.all():
-            if (appointment.status in [Appointment.APPOINTMENT_STATUS_FINISHED,
-                                       Appointment.APPOINTMENT_STATUS_SCHEDULED]) and (not (type in appointment_types)):
-                appointment_types.append(type)
-    result = False
-    for type in required_types:
-        if not (type in appointment_types):
-            result = True
-    return result
-
-
-def get_unfinished_visits(user):
-    result = []
-    for visit in get_active_visits_without_appointments(user):
-        if not waiting_for_appointment(visit):
-            result.append(visit)
-    return result
-
-
-def get_active_visits_with_missing_appointments(user):
-    result = []
-    for visit in get_active_visits_without_appointments(user):
-        if waiting_for_appointment(visit):
-            result.append(visit)
-    return result
-
-
-def get_approaching_visits_without_appointments_count(user):
-    notification = NotificationCount(
-        title="approaching visits ",
-        count=get_approaching_visits_without_appointments(user).count(),
-        style="fa fa-users text-aqua",
-        type='web.views.approaching_visits_without_appointments')
-    return notification
-
-
-def get_approaching_visits_for_mail_contact_count(user):
-    notification = NotificationCount(
-        title="post mail for approaching visits",
-        count=get_approaching_visits_for_mail_contact(user).count(),
-        style="fa fa-users text-aqua",
-        type='web.views.approaching_visits_for_mail_contact')
-    return notification
-
-
-def get_approaching_visits_without_appointments(user):
-    today = get_today_midnight_date()
-    today_plus_two_months = today + datetime.timedelta(days=91)
-    return Visit.objects.annotate(
-        my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter(
-        datetime_begin__gt=today,
-        datetime_begin__lt=today_plus_two_months,
-        is_finished=False,
-        subject__default_location__in=get_filter_locations(user),
-        my_count=0)
-
-
-def get_approaching_visits_for_mail_contact(user):
-    today = get_today_midnight_date()
-    today_plus_three_months = today + datetime.timedelta(days=91)
-    today_plus_six_months = today + datetime.timedelta(days=183)
-    return Visit.objects.annotate(
-        my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter(
-        datetime_begin__gt=today_plus_three_months,
-        datetime_begin__lt=today_plus_six_months,
-        is_finished=False,
-        post_mail_sent=False,
-        subject__default_location__in=get_filter_locations(user),
-        my_count=0)
-
-
-def get_unfinished_appointments_count(user):
-    return NotificationCount(
-        title="unfinished appointments ",
-        count=get_unfinished_appointments(user).count(),
-        style="fa fa-history text-yellow",
-        type='web.views.unfinished_appointments')
-
-
-def get_unfinished_appointments(user):
-    return Appointment.objects.filter(
-        datetime_when__lt=get_today_midnight_date(),
-        status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
-        location__in=get_filter_locations(user),
-    )
-
-
-def get_notifications(the_user):
-    workers = Worker.objects.filter(user=the_user)
-    notifications = []
-    count = 0
-    if len(workers) > 0:
-        person = workers[0]
-        if person.role == Worker.ROLE_CHOICES_SECRETARY:
-            notifications.append(get_exceeded_visit_notifications_count(person))
-            notifications.append(get_visits_without_appointments_count(person))
-            notifications.append(get_approaching_visits_without_appointments_count(person))
-            notifications.append(get_unfinished_appointments_count(person))
-            notifications.append(get_visits_with_missing_appointments_count(person))
-            notifications.append(get_subject_with_no_visit_notifications_count(person))
-            notifications.append(get_approaching_visits_for_mail_contact_count(person))
-            notifications.append(get_subjects_with_reminder_count(person))
-
-            for notification in notifications:
-                count += notification.count
-    return count, notifications
-
-
-"""
-Saturates response with information about logged user
-"""
-
-
-@login_required
-def wrap_response(request, template, params):
-    person, role = Worker.get_details(request.user)
-
-    notifications = get_notifications(request.user)
-
-    final_params = params.copy()
-    final_params.update({
-        'person': person,
-        'role': role,
-        'notifications': notifications
-    })
-
-    return render(request, template, final_params)
-
-
-def logout(request):
-    state, message = do_logout(request)
-    return redirect('/login?error=' + message)
-
-
-def visits(request):
-    visit_list = Visit.objects.order_by('-datetime_begin')
-    context = {
-        'visit_list': visit_list
-    }
-
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def exceeded_visits(request):
-    context = {
-        'visit_list': get_exceeded_visits(request.user)
-    }
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def unfinished_visits(request):
-    context = {
-        'visit_list': get_unfinished_visits(request.user)
-    }
-
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def visits_with_missing_appointments(request):
-    context = {
-        'visit_list': get_active_visits_with_missing_appointments(request.user)
-    }
-
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def approaching_visits_without_appointments(request):
-    context = {
-        'visit_list': get_approaching_visits_without_appointments(request.user)
-    }
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def approaching_visits_for_mail_contact(request):
-    context = {
-        'visit_list': get_approaching_visits_for_mail_contact(request.user)
-    }
-    return wrap_response(request, 'visits/index.html', context)
-
-
-def visit_details(request, id):
-    displayed_visit = get_object_or_404(Visit, id=id)
-    if request.method == 'POST':
-        visit_form = VisitDetailForm(request.POST, request.FILES, instance=displayed_visit)
-        if visit_form.is_valid():
-            visit_form.save()
-    else:
-        visit_form = VisitDetailForm(instance=displayed_visit)
-
-    visit_finished = displayed_visit.is_finished
-    visit_id = displayed_visit.id
-    displayed_subject = displayed_visit.subject
-    list_of_appointments = displayed_visit.appointment_set.all()
-
-    can_finish = not waiting_for_appointment(displayed_visit)
-
-    for appointment in list_of_appointments:
-        if appointment.status == Appointment.APPOINTMENT_STATUS_SCHEDULED:
-            can_finish = False
-
-    subject_form = SubjectDetailForm(instance=displayed_subject)
-
-    return wrap_response(request, 'visits/details.html', {
-        'vform': visit_form,
-        'sform': subject_form,
-        'loApp': list_of_appointments,
-        'visFinished': visit_finished,
-        'canFinish': can_finish,
-        'vid': visit_id,
-        'visit': displayed_visit})
-
-
-def visit_mark(request, id, as_what):
-    visit = get_object_or_404(Visit, id=id)
-    if as_what == 'finished':
-        visit.mark_as_finished()
-
-    return redirect(visit_details, id=id)
-
-
-def visit_add(request, subject_id=-1):
-    if request.method == 'POST':
-        form = VisitAddForm(request.POST, request.FILES)
-        if form.is_valid():
-            visit = form.save()
-            return redirect(visit_details, visit.id)
-    else:
-        subjects = Subject.objects.filter(id=subject_id)
-        subject = None
-        if len(subjects) > 0:
-            subject = subjects[0]
-        form = VisitAddForm(initial={'subject': subject})
-
-    return wrap_response(request, 'visits/add.html', {'form': form})
-
-
-def subjects(request):
-    subjects_list = Subject.objects.order_by('-last_name')
-    context = {
-        'subjects_list': subjects_list
-    }
-
-    return wrap_response(request, 'subjects/index.html', context)
-
-
-def subject_add(request):
-    if request.method == 'POST':
-        form = SubjectAddForm(request.POST, request.FILES)
-        if form.is_valid():
-            screening_number = form.cleaned_data['screening_number']
-            if screening_number == '':
-                screening_number = get_new_screening_number()  # FIXME: method doesn't exist
-            form.save()
-            return redirect(subjects)
-    else:
-        form = SubjectAddForm()
-
-    return wrap_response(request, 'subjects/add.html', {'form': form})
-
-
-def subject_no_visits(request):
-    subjects_list = get_subjects_with_no_visit(request.user).order_by('-last_name')
-    context = {
-        'subjects_list': subjects_list
-    }
-
-    return wrap_response(request, 'subjects/index.html', context)
-
-
-def subject_require_contact(request):
-    subjects_list = get_subjects_with_reminder(request.user).order_by('-last_name')
-    context = {
-        'subjects_list': subjects_list
-    }
-
-    return wrap_response(request, 'subjects/index.html', context)
-
-
-def subject_edit(request, id):
-    the_subject = get_object_or_404(Subject, id=id)
-    if request.method == 'POST':
-        form = SubjectEditForm(request.POST, request.FILES, instance=the_subject)
-        if form.is_valid():
-            form.save()
-            return redirect(subjects)
-    else:
-        form = SubjectEditForm(instance=the_subject)
-    return wrap_response(request, 'subjects/edit.html', {
-        'form': form,
-        'subject': the_subject
-    })
-
-
-def subject_delete(request, id):
-    the_subject = get_object_or_404(Subject, id=id)
-    if request.method == 'POST':
-        the_subject.delete()
-        return redirect(subjects)
-    else:
-        form = SubjectEditForm(instance=the_subject)
-    return wrap_response(request, 'subjects/delete.html', {'form': form})
-
-
-def subject_mark(request, id, as_what):
-    who = get_object_or_404(Subject, id=id)
-    if as_what == 'dead':
-        who.mark_as_dead()
-    elif as_what == 'rejected':
-        who.mark_as_rejected()
-    return redirect(subject_edit, id=id)
-
-
-def appointment_mark(request, id, as_what):
-    appointment = get_object_or_404(Appointment, id=id)
-    if as_what == 'finished':
-        appointment.mark_as_finished()
-    elif as_what == 'cancelled':
-        appointment.mark_as_cancelled()
-    elif as_what == 'no_show':
-        appointment.mark_as_no_show()
-    else:
-        return e500_error(request)
-    return redirect(visit_details, id=appointment.visit.id)
-
-
-def subject_visit_details(request, id):
-    locsubject = get_object_or_404(Subject, id=id)
-    visits = locsubject.visit_set.all()
-    endlist = []
-    for vis in visits:
-        assign = vis.appointment_set.all()
-        finished = vis.is_finished
-        visid = vis.id
-        visit_title = vis.follow_up_title()
-        visform = VisitDetailForm(instance=vis)
-        endlist.append((visform, assign, finished, visid, visit_title))
-
-    return wrap_response(request, 'subjects/visitdetails.html', {'display': endlist, "id": id})
-
-
-def doctors(request):
-    doctors_list = Worker.objects.order_by('-last_name')
-    context = {
-        'doctors_list': doctors_list
-    }
-
-    return wrap_response(request, "doctors/index.html", context)
-
-
-def doctor_add(request):
-    if request.method == 'POST':
-        form = WorkerAddForm(request.POST, request.FILES)
-        if form.is_valid():
-            form.save()
-            return redirect(doctors)
-    else:
-        form = WorkerAddForm()
-
-    return wrap_response(request, 'doctors/add.html', {'form': form})
-
-
-def doctor_edit(request, doctor_id):
-    the_doctor = get_object_or_404(Worker, id=doctor_id)
-    if request.method == 'POST':
-        form = WorkerEditForm(request.POST, request.FILES, instance=the_doctor)
-        if form.is_valid():
-            form.save()
-            return redirect(doctors)
-    else:
-        form = WorkerEditForm(instance=the_doctor)
-    return wrap_response(request, 'doctors/edit.html', {'form': form})
-
-
-def doctor_details(request, doctor_id):
-    the_doctor = get_object_or_404(Worker, id=doctor_id)
-    form = WorkerDetailForm(instance=the_doctor)
-
-    return wrap_response(request, 'doctors/details.html', {'form': form})
-
-
-def doctor_availability(request, doctor_id):
-    avall = Avaibility.objects.filter(person=doctor_id)
-
-    avmon = avall.filter(day_number=1)
-    avtue = avall.filter(day_number=2)
-    avwed = avall.filter(day_number=3)
-    avthu = avall.filter(day_number=4)
-    avfri = avall.filter(day_number=5)
-    avsat = avall.filter(day_number=6)
-    avsun = avall.filter(day_number=7)
-
-    context = {
-        'avmon': avmon,
-        'avtue': avtue,
-        'avwed': avwed,
-        'avthu': avthu,
-        'avfri': avfri,
-        'avsat': avsat,
-        'avsun': avsun,
-        'id': doctor_id
-    }
-
-    return wrap_response(request, "doctors/availability_index.html", context)
-
-
-def doctor_availability_delete(request, doctor_id, availability_id):
-    availibility = Avaibility.objects.filter(id=availability_id)
-    if len(availibility) > 0:
-        availibility.delete()
-    return redirect(doctoravail, id=doctor_id)  # FIXME doctoravail doesn't exist
-
-
-def equipment_def(request):
-    equipment_list = Item.objects.order_by('-name')
-    context = {
-        'equipment_list': equipment_list
-    }
-
-    return wrap_response(request, "eqdef/index.html", context)
-
-
-def equipment_and_rooms(request):
-    return wrap_response(request, "equipment_and_rooms/index.html", {})
-
-
-def mail_templates(request):
-    return wrap_response(request, "mail_templates/index.html", {})
-
-
-"""
-# An initial draft of a function that was supposed to suggest date, room and worker for an appointment
-def suggest_details(appoint):
-    avaibleWorkers = Worker.objects.get()
-    if appoint.appointment_type__required_worker == 'DOCTOR':
-        avaibleWorkers.filter(role='DOCTOR')
-    elif appoint.appointment_type__required_worker == 'NURSE':
-        avaibleWorkers.filter(role__in=['DOCTOR','NURSE'])
-    elif appoint.appointment_type__required_worker == 'PSYCHOLOGIST':
-        avaibleWorkers.filter(role__in=['DOCTOR','PSYCHOLOGIST'])
-    avaibleRooms = Room.objects.get
-    requireditems = appoint.appointment_type.required_equipment.filter(is_fixed=True)
-    reduce(operator.and_, (Q(equipment__contains=requireditems) for x in avaibleRooms))
-"""
-
-
-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')
-
-
-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)
-
-    context = {
-        'approaching_list': approaching_list,
-        'full_list': full_list
-    }
-
-    return wrap_response(request, "appointments/index.html", context)
-
-
-def unfinished_appointments(request):
-    appointments = get_unfinished_appointments(request.user)
-    context = {
-        'appointment_list': appointments,
-    }
-
-    return wrap_response(request, "appointments/list.html", context)
-
-
-def appointment_details(request, id):
-    the_appointment = get_object_or_404(Appointment, id=id)
-    form = AppointmentDetailForm(instance=the_appointment)
-    return wrap_response(request, 'appointments/details.html', {'form': form})
-
-
-def appointment_add(request, id):
-    full_list = get_calendar_full_appointments(request.user)
-    if request.method == 'POST':
-        form = AppointmentAddForm(request.POST, request.FILES, user=request.user)
-        form.fields['visit'].widget = HiddenInput()
-        if form.is_valid():
-            form.save()
-            return redirect(visit_details, id=id)
-    else:
-        form = AppointmentAddForm(initial={'visit': id}, user=request.user)
-        form.fields['visit'].widget = HiddenInput()
-
-    return wrap_response(request, 'appointments/add.html',
-                         {'form': form, 'visitID': id, 'full_appointment_list': full_list})
-
-
-def appointment_edit(request, id):
-    the_appointment = get_object_or_404(Appointment, id=id)
-    if request.method == 'POST':
-        appointment_form = AppointmentEditForm(request.POST,
-                                               request.FILES,
-                                               instance=the_appointment,
-                                               user=request.user,
-                                               prefix="appointment")
-        subject_form = SubjectEditForm(request.POST, instance=the_appointment.visit.subject, prefix="subject")
-
-        if appointment_form.is_valid() and subject_form.is_valid():
-            appointment_form.save()
-            subject_form.save()
-            return redirect(appointments)
-    else:
-        appointment_form = AppointmentEditForm(instance=the_appointment, user=request.user, prefix="appointment")
-
-        subject_form = SubjectEditForm(instance=the_appointment.visit.subject, prefix="subject")
-
-    return wrap_response(request, 'appointments/edit.html', {
-        'form': appointment_form,
-        'subject_form': subject_form,
-        'id': id,
-        'appointment': the_appointment
-    })
-
-
-def appointment_edit_datetime(request, id):
-    the_appointment = get_object_or_404(Appointment, id=id)
-    if request.method == 'POST':
-        form = AppointmentEditForm(request.POST, request.FILES, instance=the_appointment, user=request.user)
-        if form.is_valid():
-            form.save()
-            return redirect(appointments)
-    else:
-        the_appointment.datetime_when = the_appointment.visit.datetime_begin
-        form = AppointmentEditForm(instance=the_appointment, user=request.user)
-    return wrap_response(request, 'appointments/edit.html', {'form': form})
-
-
-# because we don't  wrap_response we must force login required
-@login_required
-def export_to_csv2(request, type="subjects"):
-    # Create the HttpResponse object with the appropriate CSV header.
-    response = HttpResponse(content_type='text/csv')
-    response['Content-Disposition'] = 'attachment; filename="' + type + '-' + get_today_midnight_date().strftime(
-        "%Y-%m-%d") + '.csv"'
-
-    writer = csv.writer(response, quotechar=str(u'"'), quoting=csv.QUOTE_ALL)
-    if type == "subjects":
-        write_subjects_to_csv(writer)
-    elif type == "appointments":
-        write_appointments_to_csv(writer)
-    else:
-        return e500_error(request)
-    return response
-
-
-def write_subjects_to_csv(writer):
-    subject_fields = []
-    for field in Subject._meta.fields:
-        if field.name != "ID":
-            subject_fields.append(field)
-
-    field_names = []
-    for field in subject_fields:
-        field_names.append(field.verbose_name)
-
-    writer.writerow(field_names)
-
-    subjects = Subject.objects.order_by('-last_name')
-    for subject in subjects:
-        row = []
-        for field in subject_fields:
-            row.append(getattr(subject, field.name))
-        writer.writerow([unicode(s).replace("\n", ";").replace("\r", ";").encode("utf-8") for s in row])
-
-
-def write_appointments_to_csv(writer):
-    appointments_fields = []
-    for field in Appointment._meta.fields:
-        if field.name != "visit" and field.name != "id" and field.name != "worker_assigned" and field.name != "appointment_types" and field.name != "room" and field.name != "flying_team":
-            appointments_fields.append(field)
-
-    field_names = ['ND number', 'Family name', 'Name', 'Visit']
-    for field in appointments_fields:
-        field_names.append(field.verbose_name)
-
-    writer.writerow(field_names)
-
-    appointments = Appointment.objects.order_by('-datetime_when')
-
-    for appointment in appointments:
-        row = [appointment.visit.subject.nd_number, appointment.visit.subject.last_name,
-               appointment.visit.subject.first_name, appointment.visit.follow_up_title()]
-        for field in appointments_fields:
-            row.append(getattr(appointment, field.name))
-        type_string = ""
-        for type in appointment.appointment_types.all():
-            type_string += type.code + ","
-        row.append(type_string)
-        writer.writerow([unicode(s).replace("\n", ";").replace("\r", ";").encode("utf-8") for s in row])
-
-
-def export(request):
-    return wrap_response(request, 'export/index.html', {})
-
-
-def get_kit_requests(user, start_date=None, end_date=None):
-    if start_date is None:
-        start_date = get_today_midnight_date() + datetime.timedelta(days=1)
-        end_date = start_date + datetime.timedelta(days=7)
-    else:
-        if isinstance(start_date, str):
-            start_date = parse_datetime(start_date)
-        if (end_date is not None) and (isinstance(end_date, str)):
-            end_date = parse_datetime(end_date)
-
-    appointment_types = AppointmentType.objects.filter(required_equipment__disposable=True)
-
-    appointments = Appointment.objects.filter(
-        appointment_types__in=appointment_types,
-        datetime_when__gt=start_date,
-        location__in=get_filter_locations(user),
-        status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
-    )
-    if end_date is not None:
-        appointments = appointments.filter(datetime_when__lt=end_date)
-
-    result = {
-        'start_date': start_date,
-        'end_date': end_date,
-        'appointments': appointments,
-    }
-    return result
-
-
-def get_kit_requests_data(request, start_date=None, end_date=None):
-    form = KitRequestForm()
-    if request.method == 'POST':
-        form = KitRequestForm(request.POST)
-        if form.is_valid():
-            form_data = form.cleaned_data
-            start_date = form_data.get('start_date')
-            end_date = form_data.get('end_date')
-
-    params = get_kit_requests(request.user, start_date, end_date)
-    params.update({
-        'form': form
-    })
-    return params
-
-
-def kit_requests(request):
-    return wrap_response(request, 'equipment_and_rooms/kit_requests.html', get_kit_requests_data(request))
-
-
-def kit_requests_send_mail(request, start_date, end_date=None):
-    return wrap_response(request, 'equipment_and_rooms/kit_requests_send_mail.html',
-                         get_kit_requests_data(request, start_date, end_date))
-
-
-def statistics(request):
-    statistics_manager = StatisticsManager()
-    visit_choices = [("-1", "all")]
-    visit_choices.extend([(rank, rank) for rank in statistics_manager.visits_ranks])
-    year_previous_month, previous_month = get_previous_year_and_month()
-
-    form = StatisticsForm(request.GET, visit_choices=visit_choices, month=previous_month, year=year_previous_month)
-    if not form.is_valid():
-        form.is_bound = False
-    month = form.data.get('month', previous_month)
-    year = form.data.get('year', year_previous_month)
-    subject_type = form.data.get('subject_type', "-1")
-    visit = form.data.get('visit', "-1")
-    if subject_type == "-1":
-        subject_type = None
-    if visit == "-1":
-        visit = None
-    monthly_statistics = statistics_manager.get_statistics_for_month(month, year, subject_type, visit)
-    return wrap_response(request, 'statistics/index.html', {
-        'form': form,
-        'monthly_statistics': monthly_statistics
-    })
diff --git a/smash/web/views/__init__.py b/smash/web/views/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..12bf76cd82f60c4af59f4559463487ae28a0b82a
--- /dev/null
+++ b/smash/web/views/__init__.py
@@ -0,0 +1,63 @@
+# coding=utf-8
+
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render
+
+from notifications import get_notifications
+from ..models import Worker
+
+__author__ = 'Valentin Grouès'
+handler404 = 'web.views.e404_page_not_found'
+handler500 = 'web.views.e500_error'
+handler403 = 'web.views.e403_permission_denied'
+handler400 = 'web.views.e400_bad_request'
+
+
+def index(request):
+    if request.user.is_authenticated():
+        return redirect('web.views.appointments')
+    return redirect('web.views.login')
+
+
+def e404_page_not_found(request, context=None):
+    return render(request, "errors/404.html", context, status=404)
+
+
+def e500_error(request, context=None):
+    return render(request, "errors/500.html", context, status=500)
+
+
+def e403_permission_denied(request, context=None):
+    return render(request, "errors/403.html", context, status=403)
+
+
+def e400_bad_request(request, context=None):
+    return render(request, "errors/400.html", context, status=400)
+
+
+@login_required
+def wrap_response(request, template, params):
+    person, role = Worker.get_details(request.user)
+
+    notifications = get_notifications(request.user)
+
+    final_params = params.copy()
+    final_params.update({
+        'person': person,
+        'role': role,
+        'notifications': notifications
+    })
+
+    return render(request, template, final_params)
+
+
+import auth
+import appointment
+import visit
+import doctor
+import subject
+import equipment
+import kit
+import mails
+import statistics
+import export
diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py
new file mode 100644
index 0000000000000000000000000000000000000000..471f33c293d3feb5a39e46127f18d9e6de7d946d
--- /dev/null
+++ b/smash/web/views/appointment.py
@@ -0,0 +1,112 @@
+# coding=utf-8
+from django.forms import HiddenInput
+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 e500_error, wrap_response
+from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, SubjectEditForm
+from ..models import Appointment
+
+__author__ = 'Valentin Grouès'
+
+
+def appointment_mark(request, id, as_what):
+    appointment = get_object_or_404(Appointment, id=id)
+    if as_what == 'finished':
+        appointment.mark_as_finished()
+    elif as_what == 'cancelled':
+        appointment.mark_as_cancelled()
+    elif as_what == 'no_show':
+        appointment.mark_as_no_show()
+    else:
+        return e500_error(request)
+    return redirect('web.views.visit_details', id=appointment.visit.id)
+
+
+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)
+
+    context = {
+        'approaching_list': approaching_list,
+        'full_list': full_list
+    }
+
+    return wrap_response(request, "appointments/index.html", context)
+
+
+def unfinished_appointments(request):
+    appointments = get_unfinished_appointments(request.user)
+    context = {
+        'appointment_list': appointments,
+    }
+
+    return wrap_response(request, "appointments/list.html", context)
+
+
+def appointment_details(request, id):
+    the_appointment = get_object_or_404(Appointment, id=id)
+    form = AppointmentDetailForm(instance=the_appointment)
+    return wrap_response(request, 'appointments/details.html', {'form': form})
+
+
+def appointment_add(request, id):
+    full_list = get_calendar_full_appointments(request.user)
+    if request.method == 'POST':
+        form = AppointmentAddForm(request.POST, request.FILES, user=request.user)
+        form.fields['visit'].widget = HiddenInput()
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.visit_details', id=id)
+    else:
+        form = AppointmentAddForm(initial={'visit': id}, user=request.user)
+        form.fields['visit'].widget = HiddenInput()
+
+    return wrap_response(request, 'appointments/add.html',
+                         {'form': form, 'visitID': id, 'full_appointment_list': full_list})
+
+
+def appointment_edit(request, id):
+    the_appointment = get_object_or_404(Appointment, id=id)
+    if request.method == 'POST':
+        appointment_form = AppointmentEditForm(request.POST,
+                                               request.FILES,
+                                               instance=the_appointment,
+                                               user=request.user,
+                                               prefix="appointment")
+        subject_form = SubjectEditForm(request.POST, instance=the_appointment.visit.subject, prefix="subject")
+
+        if appointment_form.is_valid() and subject_form.is_valid():
+            appointment_form.save()
+            subject_form.save()
+            return redirect('web.views.appointments')
+    else:
+        appointment_form = AppointmentEditForm(instance=the_appointment, user=request.user, prefix="appointment")
+
+        subject_form = SubjectEditForm(instance=the_appointment.visit.subject, prefix="subject")
+
+    return wrap_response(request, 'appointments/edit.html', {
+        'form': appointment_form,
+        'subject_form': subject_form,
+        'id': id,
+        'appointment': the_appointment
+    })
+
+
+def appointment_edit_datetime(request, id):
+    the_appointment = get_object_or_404(Appointment, id=id)
+    if request.method == 'POST':
+        form = AppointmentEditForm(request.POST, request.FILES, instance=the_appointment, user=request.user)
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.appointments')
+    else:
+        the_appointment.datetime_when = the_appointment.visit.datetime_begin
+        form = AppointmentEditForm(instance=the_appointment, user=request.user)
+    return wrap_response(request, 'appointments/edit.html', {'form': form})
diff --git a/smash/web/views/auth.py b/smash/web/views/auth.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ff4730d8e5030c28a1e7fe4159e6c981f93d168
--- /dev/null
+++ b/smash/web/views/auth.py
@@ -0,0 +1,39 @@
+# coding=utf-8
+from django.shortcuts import redirect, render
+
+from ..auth import do_login, do_logout
+
+__author__ = 'Valentin Grouès'
+
+
+def login(request):
+    context = {
+        'state': 'initial'
+    }
+    if request.GET and request.GET.get('error'):
+        context['state'] = request.GET.get('error')
+
+    if request.method == "GET" and request.GET:
+        context['next'] = request.GET.get('next')
+
+    if request.method == "POST" and request.POST:
+        state, message = do_login(request)
+        if state:
+            if request.POST.get('next'):
+                return redirect(request.POST.get('next'))
+                # FIXME: risk of phishing attacks
+                #  see https://www.owasp.org/index.php/Unvalidated_Redirects_and_Forwards_Cheat_Sheet
+            else:
+                return redirect('web.views.appointments')
+        else:
+            response = redirect('web.views.login')
+            response['Location'] += "?error={}".format(message)
+            return response
+    return render(request, "login.html", context)
+
+
+def logout(request):
+    state, message = do_logout(request)
+    response = redirect('web.views.login')
+    response['Location'] += "?error={}".format(message)
+    return response
diff --git a/smash/web/views/doctor.py b/smash/web/views/doctor.py
new file mode 100644
index 0000000000000000000000000000000000000000..a59e322198ccf22db36e2463534dbf9b7445cb13
--- /dev/null
+++ b/smash/web/views/doctor.py
@@ -0,0 +1,80 @@
+# coding=utf-8
+from django.shortcuts import redirect, get_object_or_404
+
+from . import wrap_response
+from ..forms import WorkerAddForm, WorkerEditForm, WorkerDetailForm
+from ..models import Worker, Avaibility
+
+__author__ = 'Valentin Grouès'
+
+
+def doctors(request):
+    doctors_list = Worker.objects.order_by('-last_name')
+    context = {
+        'doctors_list': doctors_list
+    }
+
+    return wrap_response(request, "doctors/index.html", context)
+
+
+def doctor_add(request):
+    if request.method == 'POST':
+        form = WorkerAddForm(request.POST, request.FILES)
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.doctors')
+    else:
+        form = WorkerAddForm()
+
+    return wrap_response(request, 'doctors/add.html', {'form': form})
+
+
+def doctor_edit(request, doctor_id):
+    the_doctor = get_object_or_404(Worker, id=doctor_id)
+    if request.method == 'POST':
+        form = WorkerEditForm(request.POST, request.FILES, instance=the_doctor)
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.doctors')
+    else:
+        form = WorkerEditForm(instance=the_doctor)
+    return wrap_response(request, 'doctors/edit.html', {'form': form})
+
+
+def doctor_details(request, doctor_id):
+    the_doctor = get_object_or_404(Worker, id=doctor_id)
+    form = WorkerDetailForm(instance=the_doctor)
+
+    return wrap_response(request, 'doctors/details.html', {'form': form})
+
+
+def doctor_availability(request, doctor_id):
+    avall = Avaibility.objects.filter(person=doctor_id)
+
+    avmon = avall.filter(day_number=1)
+    avtue = avall.filter(day_number=2)
+    avwed = avall.filter(day_number=3)
+    avthu = avall.filter(day_number=4)
+    avfri = avall.filter(day_number=5)
+    avsat = avall.filter(day_number=6)
+    avsun = avall.filter(day_number=7)
+
+    context = {
+        'avmon': avmon,
+        'avtue': avtue,
+        'avwed': avwed,
+        'avthu': avthu,
+        'avfri': avfri,
+        'avsat': avsat,
+        'avsun': avsun,
+        'id': doctor_id
+    }
+
+    return wrap_response(request, "doctors/availability_index.html", context)
+
+
+def doctor_availability_delete(request, doctor_id, availability_id):
+    availibility = Avaibility.objects.filter(id=availability_id)
+    if len(availibility) > 0:
+        availibility.delete()
+    return redirect(doctoravail, id=doctor_id)  # FIXME doctoravail doesn't exist
diff --git a/smash/web/views/equipment.py b/smash/web/views/equipment.py
new file mode 100644
index 0000000000000000000000000000000000000000..417b1f7da97263985fee54c31aad127abcadb391
--- /dev/null
+++ b/smash/web/views/equipment.py
@@ -0,0 +1,18 @@
+# coding=utf-8
+from . import wrap_response
+from ..models import Item
+
+__author__ = 'Valentin Grouès'
+
+
+def equipment_def(request):
+    equipment_list = Item.objects.order_by('-name')
+    context = {
+        'equipment_list': equipment_list
+    }
+
+    return wrap_response(request, "eqdef/index.html", context)
+
+
+def equipment_and_rooms(request):
+    return wrap_response(request, "equipment_and_rooms/index.html", {})
diff --git a/smash/web/views/export.py b/smash/web/views/export.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d5182b027ce6e08abfd72ce4390a9a1bd2bfcec
--- /dev/null
+++ b/smash/web/views/export.py
@@ -0,0 +1,78 @@
+# coding=utf-8
+import csv
+
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse
+
+from notifications import get_today_midnight_date
+from . import e500_error, wrap_response
+from ..models import Subject, Appointment
+
+__author__ = 'Valentin Grouès'
+
+
+@login_required
+def export_to_csv2(request, type="subjects"):
+    # Create the HttpResponse object with the appropriate CSV header.
+    response = HttpResponse(content_type='text/csv')
+    response['Content-Disposition'] = 'attachment; filename="' + type + '-' + get_today_midnight_date().strftime(
+        "%Y-%m-%d") + '.csv"'
+
+    writer = csv.writer(response, quotechar=str(u'"'), quoting=csv.QUOTE_ALL)
+    if type == "subjects":
+        write_subjects_to_csv(writer)
+    elif type == "appointments":
+        write_appointments_to_csv(writer)
+    else:
+        return e500_error(request)
+    return response
+
+
+def write_subjects_to_csv(writer):
+    subject_fields = []
+    for field in Subject._meta.fields:
+        if field.name != "ID":
+            subject_fields.append(field)
+
+    field_names = []
+    for field in subject_fields:
+        field_names.append(field.verbose_name)
+
+    writer.writerow(field_names)
+
+    subjects = Subject.objects.order_by('-last_name')
+    for subject in subjects:
+        row = []
+        for field in subject_fields:
+            row.append(getattr(subject, field.name))
+        writer.writerow([unicode(s).replace("\n", ";").replace("\r", ";").encode("utf-8") for s in row])
+
+
+def write_appointments_to_csv(writer):
+    appointments_fields = []
+    for field in Appointment._meta.fields:
+        if field.name != "visit" and field.name != "id" and field.name != "worker_assigned" and field.name != "appointment_types" and field.name != "room" and field.name != "flying_team":
+            appointments_fields.append(field)
+
+    field_names = ['ND number', 'Family name', 'Name', 'Visit']
+    for field in appointments_fields:
+        field_names.append(field.verbose_name)
+
+    writer.writerow(field_names)
+
+    appointments = Appointment.objects.order_by('-datetime_when')
+
+    for appointment in appointments:
+        row = [appointment.visit.subject.nd_number, appointment.visit.subject.last_name,
+               appointment.visit.subject.first_name, appointment.visit.follow_up_title()]
+        for field in appointments_fields:
+            row.append(getattr(appointment, field.name))
+        type_string = ""
+        for type in appointment.appointment_types.all():
+            type_string += type.code + ","
+        row.append(type_string)
+        writer.writerow([unicode(s).replace("\n", ";").replace("\r", ";").encode("utf-8") for s in row])
+
+
+def export(request):
+    return wrap_response(request, 'export/index.html', {})
diff --git a/smash/web/views/kit.py b/smash/web/views/kit.py
new file mode 100644
index 0000000000000000000000000000000000000000..36d8e440054732ef5be864f3fe13fc050ff23f4c
--- /dev/null
+++ b/smash/web/views/kit.py
@@ -0,0 +1,65 @@
+# coding=utf-8
+import datetime
+
+from django.utils.dateparse import parse_datetime
+
+from notifications import get_filter_locations, get_today_midnight_date
+from . import wrap_response
+from ..forms import KitRequestForm
+from ..models import AppointmentType, Appointment
+
+__author__ = 'Valentin Grouès'
+
+
+def get_kit_requests(user, start_date=None, end_date=None):
+    if start_date is None:
+        start_date = get_today_midnight_date() + datetime.timedelta(days=1)
+        end_date = start_date + datetime.timedelta(days=7)
+    else:
+        if isinstance(start_date, str):
+            start_date = parse_datetime(start_date)
+        if (end_date is not None) and (isinstance(end_date, str)):
+            end_date = parse_datetime(end_date)
+
+    appointment_types = AppointmentType.objects.filter(required_equipment__disposable=True)
+
+    appointments = Appointment.objects.filter(
+        appointment_types__in=appointment_types,
+        datetime_when__gt=start_date,
+        location__in=get_filter_locations(user),
+        status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
+    )
+    if end_date is not None:
+        appointments = appointments.filter(datetime_when__lt=end_date)
+
+    result = {
+        'start_date': start_date,
+        'end_date': end_date,
+        'appointments': appointments,
+    }
+    return result
+
+
+def get_kit_requests_data(request, start_date=None, end_date=None):
+    form = KitRequestForm()
+    if request.method == 'POST':
+        form = KitRequestForm(request.POST)
+        if form.is_valid():
+            form_data = form.cleaned_data
+            start_date = form_data.get('start_date')
+            end_date = form_data.get('end_date')
+
+    params = get_kit_requests(request.user, start_date, end_date)
+    params.update({
+        'form': form
+    })
+    return params
+
+
+def kit_requests(request):
+    return wrap_response(request, 'equipment_and_rooms/kit_requests.html', get_kit_requests_data(request))
+
+
+def kit_requests_send_mail(request, start_date, end_date=None):
+    return wrap_response(request, 'equipment_and_rooms/kit_requests_send_mail.html',
+                         get_kit_requests_data(request, start_date, end_date))
diff --git a/smash/web/views/mails.py b/smash/web/views/mails.py
new file mode 100644
index 0000000000000000000000000000000000000000..e949129f6cae5b3059ffb2cb0a5edf1dc2ac917c
--- /dev/null
+++ b/smash/web/views/mails.py
@@ -0,0 +1,8 @@
+# coding=utf-8
+from . import wrap_response
+
+__author__ = 'Valentin Grouès'
+
+
+def mail_templates(request):
+    return wrap_response(request, "mail_templates/index.html", {})
diff --git a/smash/web/views/notifications.py b/smash/web/views/notifications.py
new file mode 100644
index 0000000000000000000000000000000000000000..b3b5d5ae97b2bfec6350ca819be5644792f1cb55
--- /dev/null
+++ b/smash/web/views/notifications.py
@@ -0,0 +1,256 @@
+# coding=utf-8
+import datetime
+
+from django.contrib.auth.models import User, AnonymousUser
+from django.db.models import Count, Case, When
+
+from ..models import Worker, Subject, Visit, Appointment, Location
+
+__author__ = 'Valentin Grouès'
+
+
+class NotificationCount(object):
+    title = ""
+    count = 0
+    style = ""
+    type = ''
+
+    def __init__(self, title="Unknown", count=0, style="fa fa-users text-aqua", type='web.views.appointments'):
+        self.title = title
+        self.count = count
+        self.style = style
+        self.type = type
+
+
+def get_exceeded_visit_notifications_count(user):
+    notification = NotificationCount(
+        title="exceeded visit time",
+        count=get_exceeded_visits(user).count(),
+        style="fa fa-thermometer-4 text-red",
+        type='web.views.exceeded_visits')
+    return notification
+
+
+def get_subjects_with_reminder_count(user):
+    notification = NotificationCount(
+        title="subject required contact",
+        count=get_subjects_with_reminder(user).count(),
+        style="fa fa-users text-aqua",
+        type='web.views.subject_require_contact')
+    return notification
+
+
+def get_subject_with_no_visit_notifications_count(user):
+    notification = NotificationCount(
+        title="subject without visit",
+        count=get_subjects_with_no_visit(user).count(),
+        style="fa fa-users text-aqua",
+        type='web.views.subject_no_visits')
+    return notification
+
+
+def get_visits_without_appointments_count(user):
+    notification = NotificationCount(
+        title="unfinished visits ",
+        count=len(get_unfinished_visits(user)),
+        style="fa fa-user-times text-yellow",
+        type='web.views.unfinished_visits')
+    return notification
+
+
+def get_visits_with_missing_appointments_count(user):
+    notification = NotificationCount(
+        title="visits with missing appointments",
+        count=len(get_active_visits_with_missing_appointments(user)),
+        style="fa fa-user-times text-yellow",
+        type='web.views.visits_with_missing_appointments')
+    return notification
+
+
+def get_approaching_visits_without_appointments_count(user):
+    notification = NotificationCount(
+        title="approaching visits ",
+        count=get_approaching_visits_without_appointments(user).count(),
+        style="fa fa-users text-aqua",
+        type='web.views.approaching_visits_without_appointments')
+    return notification
+
+
+def get_approaching_visits_for_mail_contact_count(user):
+    notification = NotificationCount(
+        title="post mail for approaching visits",
+        count=get_approaching_visits_for_mail_contact(user).count(),
+        style="fa fa-users text-aqua",
+        type='web.views.approaching_visits_for_mail_contact')
+    return notification
+
+
+def get_unfinished_appointments_count(user):
+    return NotificationCount(
+        title="unfinished appointments ",
+        count=get_unfinished_appointments(user).count(),
+        style="fa fa-history text-yellow",
+        type='web.views.unfinished_appointments')
+
+
+def get_notifications(the_user):
+    workers = Worker.objects.filter(user=the_user)
+    notifications = []
+    count = 0
+    if len(workers) > 0:
+        person = workers[0]
+        if person.role == Worker.ROLE_CHOICES_SECRETARY:
+            notifications.append(get_exceeded_visit_notifications_count(person))
+            notifications.append(get_visits_without_appointments_count(person))
+            notifications.append(get_approaching_visits_without_appointments_count(person))
+            notifications.append(get_unfinished_appointments_count(person))
+            notifications.append(get_visits_with_missing_appointments_count(person))
+            notifications.append(get_subject_with_no_visit_notifications_count(person))
+            notifications.append(get_approaching_visits_for_mail_contact_count(person))
+            notifications.append(get_subjects_with_reminder_count(person))
+
+            for notification in notifications:
+                count += notification.count
+    return count, notifications
+
+
+def get_subjects_with_no_visit(user):
+    result = Subject.objects.annotate(my_count=Count(Case(When(visit__is_finished=False, then=1)))).filter(
+        dead=False,
+        resigned=False,
+        my_count=0,
+        default_location__in=get_filter_locations(user),
+        postponed=False,
+        datetime_contact_reminder__isnull=True,
+    )
+    return result
+
+
+def get_subjects_with_reminder(user):
+    tomorrow = get_today_midnight_date() + datetime.timedelta(days=1)
+
+    result = Subject.objects.filter(
+        dead=False,
+        resigned=False,
+        default_location__in=get_filter_locations(user),
+        datetime_contact_reminder__lt=tomorrow,
+    )
+    return result
+
+
+def get_active_visits_with_missing_appointments(user):
+    result = []
+    for visit in get_active_visits_without_appointments(user):
+        if waiting_for_appointment(visit):
+            result.append(visit)
+    return result
+
+
+def get_unfinished_visits(user):
+    result = []
+    for visit in get_active_visits_without_appointments(user):
+        if not waiting_for_appointment(visit):
+            result.append(visit)
+    return result
+
+
+def get_approaching_visits_without_appointments(user):
+    today = get_today_midnight_date()
+    today_plus_two_months = today + datetime.timedelta(days=91)
+    return Visit.objects.annotate(
+        my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter(
+        datetime_begin__gt=today,
+        datetime_begin__lt=today_plus_two_months,
+        is_finished=False,
+        subject__default_location__in=get_filter_locations(user),
+        my_count=0)
+
+
+def get_approaching_visits_for_mail_contact(user):
+    today = get_today_midnight_date()
+    today_plus_three_months = today + datetime.timedelta(days=91)
+    today_plus_six_months = today + datetime.timedelta(days=183)
+    return Visit.objects.annotate(
+        my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter(
+        datetime_begin__gt=today_plus_three_months,
+        datetime_begin__lt=today_plus_six_months,
+        is_finished=False,
+        post_mail_sent=False,
+        subject__default_location__in=get_filter_locations(user),
+        my_count=0)
+
+
+def get_exceeded_visits(user):
+    return Visit.objects.filter(datetime_end__lt=get_today_midnight_date(),
+                                is_finished=False,
+                                subject__default_location__in=get_filter_locations(user)
+                                ).order_by('datetime_begin')
+
+
+def get_unfinished_appointments(user):
+    return Appointment.objects.filter(
+        datetime_when__lt=get_today_midnight_date(),
+        status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
+        location__in=get_filter_locations(user),
+    )
+
+
+def waiting_for_appointment(visit):
+    required_types = visit.appointment_types.all()
+    appointment_types = []
+    for appointment in visit.appointment_set.all():
+        for type in appointment.appointment_types.all():
+            if (appointment.status in [Appointment.APPOINTMENT_STATUS_FINISHED,
+                                       Appointment.APPOINTMENT_STATUS_SCHEDULED]) and (not (type in appointment_types)):
+                appointment_types.append(type)
+    result = False
+    for type in required_types:
+        if not (type in appointment_types):
+            result = True
+    return result
+
+
+def get_active_visits_without_appointments(user):
+    today = get_today_midnight_date()
+    return Visit.objects.annotate(
+        my_count=Count(Case(When(appointment__status=Appointment.APPOINTMENT_STATUS_SCHEDULED, then=1)))).filter(
+        datetime_begin__lt=today,
+        datetime_end__gt=today,
+        is_finished=False,
+        subject__default_location__in=get_filter_locations(user),
+        my_count=0)
+
+
+def get_filter_locations(user):
+    worker = None
+
+    if isinstance(user, User):
+        workers = Worker.objects.filter(user=user)
+        if len(workers) > 0:
+            worker = workers[0]
+    elif isinstance(user, Worker):
+        worker = user
+    elif isinstance(user, AnonymousUser):
+        # anonymous user shouldn't see anything
+        return Location.objects.filter(id=-1)
+    elif user is not None:
+        raise TypeError("Unknown class type: " + user.__class__.__name__)
+
+    if worker is None or worker.locations.count() == 0:
+        return Location.objects.all()
+    else:
+        return worker.locations.all()
+
+
+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')
diff --git a/smash/web/views/statistics.py b/smash/web/views/statistics.py
new file mode 100644
index 0000000000000000000000000000000000000000..486427f5f6d4ac18de9606b121378b4f24121ce7
--- /dev/null
+++ b/smash/web/views/statistics.py
@@ -0,0 +1,30 @@
+# coding=utf-8
+from . import wrap_response
+from ..forms import StatisticsForm
+from ..statistics import StatisticsManager, get_previous_year_and_month
+
+__author__ = 'Valentin Grouès'
+
+
+def statistics(request):
+    statistics_manager = StatisticsManager()
+    visit_choices = [("-1", "all")]
+    visit_choices.extend([(rank, rank) for rank in statistics_manager.visits_ranks])
+    year_previous_month, previous_month = get_previous_year_and_month()
+
+    form = StatisticsForm(request.GET, visit_choices=visit_choices, month=previous_month, year=year_previous_month)
+    if not form.is_valid():
+        form.is_bound = False
+    month = form.data.get('month', previous_month)
+    year = form.data.get('year', year_previous_month)
+    subject_type = form.data.get('subject_type', "-1")
+    visit = form.data.get('visit', "-1")
+    if subject_type == "-1":
+        subject_type = None
+    if visit == "-1":
+        visit = None
+    monthly_statistics = statistics_manager.get_statistics_for_month(month, year, subject_type, visit)
+    return wrap_response(request, 'statistics/index.html', {
+        'form': form,
+        'monthly_statistics': monthly_statistics
+    })
diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8987167f2c7db1be39b577d660853862ace54a1
--- /dev/null
+++ b/smash/web/views/subject.py
@@ -0,0 +1,101 @@
+# coding=utf-8
+
+from django.shortcuts import redirect, get_object_or_404
+
+from notifications import get_subjects_with_no_visit, get_subjects_with_reminder
+from . import wrap_response
+from ..forms import SubjectAddForm, SubjectEditForm, VisitDetailForm
+from ..models import Subject
+
+__author__ = 'Valentin Grouès'
+
+
+def subjects(request):
+    subjects_list = Subject.objects.order_by('-last_name')
+    context = {
+        'subjects_list': subjects_list
+    }
+
+    return wrap_response(request, 'subjects/index.html', context)
+
+
+def subject_add(request):
+    if request.method == 'POST':
+        form = SubjectAddForm(request.POST, request.FILES)
+        if form.is_valid():
+            screening_number = form.cleaned_data['screening_number']
+            if screening_number == '':
+                screening_number = get_new_screening_number()  # FIXME: method doesn't exist
+            form.save()
+            return redirect('web.views.subjects')
+    else:
+        form = SubjectAddForm()
+
+    return wrap_response(request, 'subjects/add.html', {'form': form})
+
+
+def subject_no_visits(request):
+    subjects_list = get_subjects_with_no_visit(request.user).order_by('-last_name')
+    context = {
+        'subjects_list': subjects_list
+    }
+
+    return wrap_response(request, 'subjects/index.html', context)
+
+
+def subject_require_contact(request):
+    subjects_list = get_subjects_with_reminder(request.user).order_by('-last_name')
+    context = {
+        'subjects_list': subjects_list
+    }
+
+    return wrap_response(request, 'subjects/index.html', context)
+
+
+def subject_edit(request, id):
+    the_subject = get_object_or_404(Subject, id=id)
+    if request.method == 'POST':
+        form = SubjectEditForm(request.POST, request.FILES, instance=the_subject)
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.subjects')
+    else:
+        form = SubjectEditForm(instance=the_subject)
+    return wrap_response(request, 'subjects/edit.html', {
+        'form': form,
+        'subject': the_subject
+    })
+
+
+def subject_delete(request, id):
+    the_subject = get_object_or_404(Subject, id=id)
+    if request.method == 'POST':
+        the_subject.delete()
+        return redirect('web.views.subjects')
+    else:
+        form = SubjectEditForm(instance=the_subject)
+    return wrap_response(request, 'subjects/delete.html', {'form': form})
+
+
+def subject_mark(request, id, as_what):
+    who = get_object_or_404(Subject, id=id)
+    if as_what == 'dead':
+        who.mark_as_dead()
+    elif as_what == 'rejected':
+        who.mark_as_rejected()
+    return redirect('web.views.subject_edit', id=id)
+
+
+def subject_visit_details(request, id):
+    locsubject = get_object_or_404(Subject, id=id)
+    visits = locsubject.visit_set.all()
+    endlist = []
+    for vis in visits:
+        assign = vis.appointment_set.all()
+        finished = vis.is_finished
+        visid = vis.id
+        visit_title = vis.follow_up_title()
+        visform = VisitDetailForm(instance=vis)
+        endlist.append((visform, assign, finished, visid, visit_title))
+
+    return wrap_response(request, 'subjects/visitdetails.html', {'display': endlist, "id": id})
diff --git a/smash/web/views/visit.py b/smash/web/views/visit.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfce9763c938cc7e0f467320a99c5b48e40d3ab8
--- /dev/null
+++ b/smash/web/views/visit.py
@@ -0,0 +1,113 @@
+# coding=utf-8
+from django.shortcuts import get_object_or_404, redirect
+
+from notifications import get_active_visits_with_missing_appointments, get_unfinished_visits, \
+    get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits, \
+    waiting_for_appointment
+from . import wrap_response
+from ..forms import VisitDetailForm, SubjectDetailForm, VisitAddForm
+from ..models import Visit, Appointment, Subject
+
+__author__ = 'Valentin Grouès'
+
+
+def visits(request):
+    visit_list = Visit.objects.order_by('-datetime_begin')
+    context = {
+        'visit_list': visit_list
+    }
+
+    return wrap_response(request, 'visits/index.html', context)
+
+
+def visits_with_missing_appointments(request):
+    context = {
+        'visit_list': get_active_visits_with_missing_appointments(request.user)
+    }
+
+    return wrap_response(request, 'visits/index.html', context)
+
+
+def approaching_visits_without_appointments(request):
+    context = {
+        'visit_list': get_approaching_visits_without_appointments(request.user)
+    }
+    return wrap_response(request, 'visits/index.html', context)
+
+
+def approaching_visits_for_mail_contact(request):
+    context = {
+        'visit_list': get_approaching_visits_for_mail_contact(request.user)
+    }
+    return wrap_response(request, 'visits/index.html', context)
+
+
+def visit_details(request, id):
+    displayed_visit = get_object_or_404(Visit, id=id)
+    if request.method == 'POST':
+        visit_form = VisitDetailForm(request.POST, request.FILES, instance=displayed_visit)
+        if visit_form.is_valid():
+            visit_form.save()
+    else:
+        visit_form = VisitDetailForm(instance=displayed_visit)
+
+    visit_finished = displayed_visit.is_finished
+    visit_id = displayed_visit.id
+    displayed_subject = displayed_visit.subject
+    list_of_appointments = displayed_visit.appointment_set.all()
+
+    can_finish = not waiting_for_appointment(displayed_visit)
+
+    for appointment in list_of_appointments:
+        if appointment.status == Appointment.APPOINTMENT_STATUS_SCHEDULED:
+            can_finish = False
+
+    subject_form = SubjectDetailForm(instance=displayed_subject)
+
+    return wrap_response(request, 'visits/details.html', {
+        'vform': visit_form,
+        'sform': subject_form,
+        'loApp': list_of_appointments,
+        'visFinished': visit_finished,
+        'canFinish': can_finish,
+        'vid': visit_id,
+        'visit': displayed_visit})
+
+
+def visit_mark(request, id, as_what):
+    visit = get_object_or_404(Visit, id=id)
+    if as_what == 'finished':
+        visit.mark_as_finished()
+
+    return redirect('web.views.visit_details', id=id)
+
+
+def visit_add(request, subject_id=-1):
+    if request.method == 'POST':
+        form = VisitAddForm(request.POST, request.FILES)
+        if form.is_valid():
+            visit = form.save()
+            return redirect('web.views.visit_details', visit.id)
+    else:
+        subjects = Subject.objects.filter(id=subject_id)
+        subject = None
+        if len(subjects) > 0:
+            subject = subjects[0]
+        form = VisitAddForm(initial={'subject': subject})
+
+    return wrap_response(request, 'visits/add.html', {'form': form})
+
+
+def exceeded_visits(request):
+    context = {
+        'visit_list': get_exceeded_visits(request.user)
+    }
+    return wrap_response(request, 'visits/index.html', context)
+
+
+def unfinished_visits(request):
+    context = {
+        'visit_list': get_unfinished_visits(request.user)
+    }
+
+    return wrap_response(request, 'visits/index.html', context)