diff --git a/requirements.txt b/requirements.txt
index 01f45824f0cbf1d4f885b222807a543dcaf2de59..430b5f8755f21bbc65da2b720da97812fb6b0288 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,3 +18,7 @@ pyexcel==0.5.3
 pycurl==7.43.0
 django-stronghold==0.2.9
 timeout-decorator==0.4.0
+Unidecode==1.0.22
+Faker==0.9.2
+pandas==0.23.4
+numpy==1.15.2
\ No newline at end of file
diff --git a/smash/create_dummy_data.py b/smash/create_dummy_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..9379af3675344dee833a5191c34c0f63e59ef24a
--- /dev/null
+++ b/smash/create_dummy_data.py
@@ -0,0 +1,715 @@
+from django.conf import settings
+from django.core.files import File  # you need this somewhere
+import urllib
+from django.core.files.uploadedfile import SimpleUploadedFile
+import django
+import datetime
+from django.utils import timezone
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "smash.settings")
+django.setup()
+from django.contrib.auth.models import User
+# models (please add in both lines)
+from web.models import StudySubject, Availability, Visit, Appointment, AppointmentType, AppointmentTypeLink, Study, Subject, Worker, Location, Language, Country, WorkerStudyRole, Item, FlyingTeam, Room, MailTemplate
+from smash.local_settings import MEDIA_ROOT
+from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \
+    SEX_CHOICES_MALE, SEX_CHOICES_FEMALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT, CONTACT_TYPES_PHONE, \
+    MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID, VOUCHER_STATUS_NEW, GLOBAL_STUDY_ID, DEFAULT_LOCALE_NAME
+from web.models.constants import MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_VISIT, \
+    MAIL_TEMPLATE_CONTEXT_SUBJECT, MAIL_TEMPLATE_CONTEXT_VOUCHER
+from web.models.worker_study_role import ROLE_CHOICES_PROJECT_MANAGER, ROLE_CHOICES_SECRETARY, ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER, ROLE_CHOICES_TECHNICIAN, ROLE_CHOICES_PSYCHOLOGIST, ROLE_CHOICES_NURSE
+from web.tests.functions import get_resource_path
+
+from collections import defaultdict
+import logging
+logger = logging.getLogger(__name__)
+
+from web.views.notifications import get_today_midnight_date
+from faker.providers import BaseProvider, color
+from numpy.random import choice
+from faker import Faker
+import platform
+import tempfile
+import unidecode
+from shutil import copyfile
+
+
+class smashProvider(BaseProvider):
+
+    __provider__ = 'smash'
+    __lang__ = 'fr_FR'
+    fake = Faker()
+    fake.seed(4321)
+
+    specialists = ['Psychiatrist', 'Radiologist', 'Immunologist', 'Anesthesiologist',
+                   'Surgeon', 'Pediatrician', 'Neurologist', 'Medical examiner']
+    units = ['LHI', 'PCR', 'LCSB', 'ACO', 'PLI', 'LRSU']
+    workerRoles = [ROLE_CHOICES_PROJECT_MANAGER, ROLE_CHOICES_SECRETARY, ROLE_CHOICES_DOCTOR,
+                   ROLE_CHOICES_TECHNICIAN, ROLE_CHOICES_PSYCHOLOGIST, ROLE_CHOICES_NURSE]
+
+    #towns and cities
+    near_fr_towns = ['Metz', 'Audun-le-Tiche', 'Thionville']
+    near_de_towns = ['Trier']
+    near_be_towns = ['Aubange', 'Arlon']
+    luxtowns = ['Luxembourg City', 'Esch-sur-Alzette', 'Differdange', 'Dudelange', 'Ettelbruck',
+                'Diekirch', 'Wiltz', 'Rumelange', 'Echternach', 'Grevenmacher', 'Remich', 'Vianden']
+
+    # if you add or remove cities, be careful and change the probabilities as well
+    # here the probs ensure the luxembourgish towns have 0.8 prob, and so on...
+    cities = luxtowns + near_fr_towns + near_de_towns + near_be_towns
+    city_prob = [0.8 / len(luxtowns)]      * len(luxtowns) + \
+        [0.1 / len(near_fr_towns)] * len(near_fr_towns) + \
+        [0.05 / len(near_de_towns)] * len(near_de_towns) + \
+        [0.05 / len(near_be_towns)] * len(near_be_towns)
+
+    alreadyCreatedLocations = {}
+    # languages
+    language_base_dir = 'web/static/flags/'
+    language_flags = {
+        'French': 'FR.png',
+        'English': 'GB.png',
+        'Spanish': 'ES.png',
+        'Portuguese': 'PT.png',
+        'Luxembourgish': 'LU.png',
+        'German': 'DE.png',
+        'Dutch': 'NL.png'
+    }
+    language_locale = {
+        'French': 'fr_FR',
+        'Luxembourgish': 'lb_LU',
+        'Spanish': 'es_ES',
+        'English': 'en_GB',
+        'Portuguese': 'pt_PT',
+        'German': 'de_DE',
+        'Dutch': 'nl_NL'
+    }
+    languages = ['English', 'Luxembourgish', 'French',
+                 'German', 'Portuguese', 'Spanish', 'Dutch']
+    languages_prob = [0.25, 0.2, 0.16, 0.16, 0.13, 0.05, 0.05]
+    alreadyCreatedLanguages = {}
+    # items
+    items = ['Saliva', 'Blood', 'Liquid Biopsy',
+             'X-Ray Examination', 'Urine', 'Tissue Biopsy', 'Skin sample']
+    alreadyCreatedItems = {}
+    # flying teams
+    places = ['Belval', 'Belvaux', 'Arlon', 'Metz']
+    alreadyCreatedFlyingTeams = {}
+    # countries
+    countries = ['France', 'Luxembourg', 'Germamny', 'Belgium']
+    alreadyCreatedCountries = {}
+    # screening number
+    screening_number_ctrs = defaultdict(int)
+    # template file
+    template_file = get_resource_path('upcoming_appointment_FR.docx')
+    template_context = [MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_VOUCHER,
+                        MAIL_TEMPLATE_CONTEXT_VISIT, MAIL_TEMPLATE_CONTEXT_SUBJECT]
+    # subjects
+    alreadyCreatedSubjects = []
+    alreadyCreatedStudySubjects = []
+    # appointment type
+    alreadyCreatedAppointmentTypes = []
+    # workers
+    alreadyCreatedWorkers = []
+    # rooms
+    alreadyCreatedRooms = []
+    # nd_number_ctr
+    nd_number_ctr = 0
+
+    def getLuxembourgTown(self):
+        return self.fake.word(ext_word_list=self.luxtowns)
+
+    def getFrenchTown(self):
+        return self.fake.word(ext_word_list=self.near_fr_towns)
+
+    def getBelgiumTown(self):
+        return self.fake.word(ext_word_list=self.near_be_towns)
+
+    def getDeutschTown(self):
+        return self.fake.word(ext_word_list=self.near_de_towns)
+
+    def getWorkerRole(self):
+        return self.fake.word(ext_word_list=self.workerRoles)
+
+    # availability
+    def createSmashAvailabilities(self):
+        for worker in self.alreadyCreatedWorkers:
+            for weekday in set(choice(range(1, 6), 4)):
+                availability = self.createSmashAvailability(
+                    worker=worker, day_number=weekday)
+
+    def createSmashAvailability(self, worker=None, day_number=None, available_from=None, available_till=None):
+        if worker is None:
+            worker = choice(self.alreadyCreatedWorkers)
+        if day_number is None:
+            day_number = choice(range(1, 6))
+        if available_from is None:
+            available_from = '{}:00'.format(choice(range(8, 10)))
+        if available_till is None:
+            available_till = '{}:00'.format(choice(range(13, 18)))
+
+        availability, _ = Availability.objects.update_or_create(person=worker,
+                                                                day_number=day_number,
+                                                                available_from=available_from,
+                                                                available_till=available_till)
+        return availability
+
+    # appointments
+    def createSmashAppointments(self):
+        today = timezone.now()
+        # create first visit, in between -2y and -1y from now and first
+        # appointment
+        for studySubject in self.alreadyCreatedStudySubjects:
+            # date_between does not support months so we use days
+            visit_start_date = fake.date_between(
+                start_date='-1y', end_date='-334d') - datetime.timedelta(days=-31)
+            visit_end_date = visit_start_date + datetime.timedelta(days=31)
+            visit = self.createSmashVisit(studySubject=studySubject, datetime_begin=visit_start_date,
+                                          datetime_end=visit_end_date, is_finished=True)
+            appointment_types = self.getAlreadyCreatedAppointmentTypes()
+            visit.appointment_types.set(appointment_types)
+            # appointment
+            duration = sum([app.default_duration for app in appointment_types])
+            appointment_start_date = fake.date_time_between(start_date=visit_start_date, end_date=visit_end_date,
+                                                            tzinfo=today.tzinfo)
+            hour = choice(range(9, 18))
+            minute = choice(range(0, 30, 5))
+            # ensure the time is between office hours
+            appointment_start_date = appointment_start_date.replace(
+                hour=hour, minute=minute, tzinfo=today.tzinfo)
+
+            room = choice(self.alreadyCreatedRooms)
+            appointment = self.createSmashAppointment(visit=visit, datetime_when=appointment_start_date,
+                                                      length=duration, appointment_types=appointment_types, room=room, status=Appointment.APPOINTMENT_STATUS_FINISHED)
+
+    def createSmashAppointment(self, visit=None, datetime_when=None, location=None, length=30,
+                               status=Appointment.APPOINTMENT_STATUS_SCHEDULED, appointment_types=None, room=None, worker_assigned=None):
+        if visit is None:
+            visit = self.createSmashVisit()
+        if datetime_when is None:
+            datetime_when = get_today_midnight_date()
+        if location is None:
+            location = self.getAlreadyCreatedSmashLocation()
+        if room is None:
+            room = choice(self.alreadyCreatedRooms)
+        if worker_assigned is None:
+            worker_assigned = choice(self.alreadyCreatedWorkers)
+
+        appointment, _ = Appointment.objects.update_or_create(
+            visit=visit, length=length, location=location, worker_assigned=worker_assigned,
+            status=status, datetime_when=datetime_when, room=room)
+
+        if appointment_types is None:
+            appointment_types = self.getAlreadyCreatedAppointmentTypes()
+
+        # create connection for each appointment type
+        when = datetime_when
+        for appointment_type in appointment_types:
+            worker = choice(self.alreadyCreatedWorkers)
+            when = when + \
+                datetime.timedelta(minutes=appointment_type.default_duration)
+            app_type_link = AppointmentTypeLink(appointment=appointment, date_when=when,
+                                                appointment_type=appointment_type, worker=worker)
+
+        return appointment
+
+    # visit
+    def createSmashVisit(self, datetime_begin=None, datetime_end=None, studySubject=None, is_finished=False, visit_number=1):
+        if datetime_begin is None:
+            datetime_begin = get_today_midnight_date() + datetime.timedelta(days=-31)
+        if datetime_end is None:
+            datetime_end = get_today_midnight_date() + datetime.timedelta(days=31)
+        if studySubject is None:
+            studySubject = choice(self.alreadyCreatedStudySubjects)
+
+        visit, _ = Visit.objects.update_or_create(datetime_begin=datetime_begin, datetime_end=datetime_end,
+                                                  subject=studySubject, is_finished=is_finished, visit_number=visit_number)
+        return visit
+
+    # AppointmentTypes
+    def getAlreadyCreatedAppointmentTypes(self, n=3):
+        return set(choice(self.alreadyCreatedAppointmentTypes, n))
+
+    def createSmashAppointmentTypes(self, n_max=5):
+        return [self.createSmashAppointmentType() for _ in xrange(n_max)]
+
+    def createSmashAppointmentType(self, code=None, default_duration=10, rest_time=5, description=None):
+        if code is None:
+            code = self.fake.word(ext_word_list=['C', 'A', 'N', 'P'])
+        if description is None:
+            description = self.fake.word(
+                ext_word_list=['Examination', 'Analysis', 'Questions', 'Test'])
+        equipment = self.getSmashAlreadyCreatedItems()
+        appointmentType, _ = AppointmentType.objects.update_or_create(code=code, rest_time=rest_time,
+                                                                      default_duration=default_duration, description=description)
+
+        appointmentType.required_equipment.set(equipment)
+        appointmentType.required_worker = choice(
+            ['DOCTOR', 'NURSE', 'PSYCHOLOGIST', 'ANY'])
+        appointmentType.save()
+
+        self.alreadyCreatedAppointmentTypes.append(appointmentType)
+
+        return appointmentType
+
+    # mail template
+    def createSmashMailTemplates(self):
+        '''
+        Returns a list of template objects
+        '''
+        languages = self.getAlreadyCreatedSmashLanguages()
+        templates = []
+        for context in self.template_context:
+            for language in languages:
+                template = self.createSmashMailTemplate(
+                    language=language, context=context)
+                templates.append(template)
+        return templates
+
+    def createSmashMailTemplate(self, name=None, language=None, context=MAIL_TEMPLATE_CONTEXT_APPOINTMENT, template_file=None):
+        if language is None:
+            language = self.getAlreadyCreatedSmashLanguages()[0]
+        if name is None:
+            name = '{} Template'.format(language.name)
+        if template_file is None:
+            template_file = self.template_file
+            basename = os.path.basename(template_file)
+            dst = os.path.join(MEDIA_ROOT, basename)
+            copyfile(template_file, dst)
+        template, _ = MailTemplate.objects.update_or_create(
+            name=name, language=language, context=context, template_file=dst)
+        return template
+
+    # flying teams
+    def createSmashFlyingTeams(self):
+        return [self.createSmashFlyingTeam(place=place) for place in self.places]
+
+    def createSmashFlyingTeam(self, place=None):
+        if place is None:
+            place = self.fake.word(ext_word_list=self.places)
+        flying_team, _ = FlyingTeam.objects.update_or_create(place=place)
+
+        self.alreadyCreatedFlyingTeams[place] = flying_team
+
+        return flying_team
+
+    # item
+    def createSmashItems(self):
+        '''
+        Returns a list of Item objects
+        '''
+        return [self.createSmashItem(name=item) for item in self.items]
+
+    def createSmashItem(self, name=None, is_fixed=None, disposable=None):
+        '''
+        Returns an Item object
+        '''
+        if name is None:
+            name = self.fake.word(ext_word_list=self.items)
+
+        if is_fixed is None:
+            is_fixed = self.fake.boolean(chance_of_getting_true=75)
+
+        if disposable is None:
+            disposable = self.fake.boolean(chance_of_getting_true=75)
+
+        item, _ = Item.objects.update_or_create(
+            name=name, is_fixed=is_fixed, disposable=disposable)
+
+        self.alreadyCreatedItems[name] = item
+
+        return item
+
+    def getSmashAlreadyCreatedItems(self, max_n=3):
+        if len(self.alreadyCreatedItems.keys()) == 0:
+            self.createSmashItems()
+
+        keys = set(choice(self.alreadyCreatedItems.keys(), max_n))
+        return [self.alreadyCreatedItems[key] for key in keys]
+
+    # room
+    def createSmashRooms(self, n=4):
+        return [self.createSmashRoom() for _ in xrange(n)]
+
+    def createSmashRoom(self, equipment=None, owner=None, address=None, city=None, room_number=None, floor=None, is_vehicle=None):
+        '''
+        equipment: List of Item objects: 
+        owner: string: 
+        address: string: 
+        city: string: 
+        room_number: int: 
+        floor: int: 
+        is_vehicle: boolean: 
+        '''
+        if equipment is None:
+            item = self.fake.word(
+                ext_word_list=self.alreadyCreatedItems.keys())
+            equipment = self.getSmashAlreadyCreatedItems()
+
+        if owner is None:
+            owner = self.fake.first_name()
+
+        if address is None:
+            address = '{}, {} {}'.format(self.fake.word(
+                ext_word_list=self.cities), self.fake.random_digit_not_null_or_empty(), self.fake.street_name())
+            address = address.replace('\n', ' ')
+
+        if city is None:
+            city = self.fake.word(ext_word_list=self.cities)
+
+        if room_number is None:
+            room_number = choice(range(10), 1)[0]  # choice returns a list
+
+        if floor is None:
+            floor = choice(range(10), 1)[0]
+
+        if is_vehicle is None:
+            is_vehicle = self.fake.boolean(chance_of_getting_true=25)
+
+        dicc = {'owner': owner, 'city': city, 'address': address,
+                'floor': floor, 'room_number': room_number, 'is_vehicle': is_vehicle}
+        room, _ = Room.objects.update_or_create(**dicc)
+        room.equipment.set(equipment)
+        room.save()
+
+        self.alreadyCreatedRooms.append(room)
+
+        return room
+
+    # country
+    def getAlreadyCreatedSmashCountry(self):
+        if len(self.alreadyCreatedCountries) == 0:
+            self.createSmashCountries()
+
+        name = self.fake.word(ext_word_list=self.countries)
+        return self.alreadyCreatedCountries[name]
+
+    def createSmashCountries(self):
+        return [self.createSmashCountry(name=country) for country in self.countries]
+
+    def createSmashCountry(self, name=None, order=0):
+        if name is None:
+            name = self.fake.word(ext_word_list=self.countries)
+        country, _ = Country.objects.update_or_create(name=name, order=order)
+        self.alreadyCreatedCountries[name] = country
+        return country
+
+    def createSmashScreeningNumber(self, prefix):
+        self.screening_number_ctrs[prefix] += 1
+        return '{}-{:03}'.format(prefix, self.screening_number_ctrs[prefix])
+
+    # subject
+    def createSmashStudySubjects(self, n=30):
+        '''
+        Returns a list of tuples (subject, study_subject)
+        '''
+        study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+        return [self.createSmashStudySubject(study=study) for _ in xrange(n)]
+
+    def createSmashStudySubject(self, nd_number=None, subject=None, study=None, postponed=None,
+                                datetime_contact_reminder=None, type=None, default_location=None, flying_team=None,
+                                screening_number=None):  # complete code and args...
+
+        if nd_number is None:
+            nd_number = 'ND{:04}'.format(self.nd_number_ctr)
+            self.nd_number_ctr += 1
+
+        if subject is None:
+            subject = self.createSmashSubject()
+
+        if study is None:
+            study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+        if type is None:
+            type = choice([SUBJECT_TYPE_CHOICES_CONTROL,
+                           SUBJECT_TYPE_CHOICES_PATIENT], 1, p=[0.2, 0.8])
+            type = type[0]
+
+        if default_location is None:
+            default_location = self.getAlreadyCreatedSmashLocation()
+
+        if screening_number is None:
+            screening_number = self.createSmashScreeningNumber(
+                default_location.prefix)
+
+        study_subject, _ = StudySubject.objects.update_or_create(nd_number=nd_number, subject=subject,
+                                                                 defaults={'default_location': default_location, 'type': type,
+                                                                           'screening_number': screening_number, 'study': study})
+
+        self.alreadyCreatedStudySubjects.append(study_subject)
+
+        return study_subject
+
+    def createSmashSubject(self, first_name=None, last_name=None, languages=None,
+                           default_written_communication_language=None, phone_number=None, social_security_number=None,
+                           sex=None, country_id=None, date_born=None, address=None,
+                           postal_code=None, city=None, country=None, dead=None):
+
+        if first_name is None:
+            first_name = self.fake.first_name()
+
+        if last_name is None:
+            last_name = self.fake.last_name()
+
+        if languages is None:
+            languages = self.getAlreadyCreatedSmashLanguages()
+
+        if default_written_communication_language is None:
+            getAlreadyCreatedSmashLanguages = languages[0]
+
+        if phone_number is None:
+            phone_number = self.fake.phone_number()
+
+        if social_security_number is None:
+            social_security_number = self.fake.ean(length=8)
+
+        if sex is None:
+            sex = self.fake.word(
+                ext_word_list=[SEX_CHOICES_MALE, SEX_CHOICES_FEMALE])
+
+        if country_id is None:
+            country_id = COUNTRY_AFGHANISTAN_ID
+
+        if date_born is None:
+            date_born = self.fake.date_of_birth(
+                minimum_age=50, maximum_age=105).strftime('%Y-%m-%d')
+        if address is None:
+            address = self.fake.address()
+        if postal_code is None:
+            postal_code = self.fake.postcode()
+        if city is None:
+            city = self.fake.word(ext_word_list=self.cities)
+        if country is None:
+            country = self.getAlreadyCreatedSmashCountry()
+        if dead is None:
+            dead = self.fake.boolean(chance_of_getting_true=1)
+
+        dicc = {'first_name': first_name, 'last_name': last_name,
+                'default_written_communication_language': default_written_communication_language,
+                'phone_number': phone_number, 'social_security_number': social_security_number,
+                'sex': sex, 'country_id': country_id, 'date_born': date_born, 'address': address,
+                'postal_code': postal_code, 'city': city, 'country': country, 'dead': dead}
+
+        subject, _ = Subject.objects.update_or_create(**dicc)
+        subject.languages.set(languages)
+        subject.save()
+
+        self.alreadyCreatedSubjects.append(subject)
+
+        return subject
+
+    # user
+    def createSmashUser(self, username=None, email=None, password='smashtest007'):
+        '''
+        username: string: Sets the username. By default None. Random values are generated unless other value is provided.
+        email: string: Sets the email. By default None. Random values are generated unless other value is provided.
+        password: string: Sets the password. By default password1234 unless other value is provided.
+        Returns a user
+        '''
+        first_name = self.fake.first_name().lower()
+        last_name = self.fake.last_name().lower()
+        if username is None:
+            username = u'{}_{}'.format(first_name, last_name)
+        username = unidecode.unidecode(username).lower().replace(' ', '_')
+
+        if email is None:
+            email = u'{}.{}@smash.lu'.format(first_name, last_name)
+        email = unidecode.unidecode(email)
+        # create user
+        defaults = {'email': email, 'password': password}
+        user, _ = User.objects.update_or_create(
+            username=username, defaults=defaults)
+        user.set_password(password)
+        user.save()
+        return user
+
+    # worker
+    def createSmashWorkers(self, n=10):
+        '''
+        :n int: number of workers to create
+
+        Returns a list of tuples (user, worker, workerStuyRole)
+        '''
+        logger.info('creating Smash Worker...')
+        return [self.createSmashWorker() for _ in xrange(n)]
+
+    def createSmashWorker(self, first_name=None, last_name=None, username=None, email=None,
+                          specialization=None, unit=None, phone_number=None, password='password1234', role=None,
+                          locations=None, languages=None):
+        '''
+        first_name: string: Sets the first_name. By default None. Random values are generated. Provide a value to overwrite random generation
+        last_name: string: Sets the last_name. By default None. Random values are generated. Provide a value to overwrite random generation
+        username: string: Sets the username. By default None. Random values are generated. Provide a value to overwrite random generation
+        email: string: Sets the email. By default None. Random values are generated. Provide a value to overwrite random generation
+        specialization: string: Sets the specialization. By default None. Random values are generated. Provide a value to overwrite random generation
+        unit: string: Sets the unit. By default None. Random values are generated. Provide a value to overwrite random generation
+        phone_number: string: Sets the phone_number. By default None. Random values are generated. Provide a value to overwrite random generation
+        password: string: Sets the password. By default None. Random values are generated. Provide a value to overwrite random generation
+        role: string: Sets the role. By default None. Random values are generated. Provide a value to overwrite random generation
+        locations: list: Sets the worker locations. By default None. Random values are generated. Provide a value to overwrite random generation
+        languages: list: Sets the worker languages. By default None. Random values are generated. Provide a value to overwrite random generation
+        Returns a tuple with (user, worker, workerStuyRole)
+        '''
+        if first_name is None:
+            first_name = self.fake.first_name()
+
+        if last_name is None:
+            last_name = self.fake.last_name()
+
+        if username is None:
+            username = u'{}_{}'.format(
+                first_name, last_name).lower().replace(' ', '_')
+
+        if email is None:
+            email = u'{}.{}@smash.lu'.format(first_name, last_name)
+
+        if specialization is None:
+            specialization = self.getSmashSpecialization()
+
+        if unit is None:
+            unit = self.getSmashUnit()
+
+        if phone_number is None:
+            phone_number = self.fake.phone_number()
+
+        if role is None:
+            role = self.getWorkerRole()
+
+        # create user
+        user = self.createSmashUser(
+            username=username, email=email, password=password)
+
+        # create worker
+        defaults = {'first_name': first_name, 'last_name': last_name,
+                    'email': email, 'unit': unit, 'specialization': specialization,
+                    'phone_number': phone_number, 'user': user}
+        worker, _ = Worker.objects.update_or_create(first_name=first_name,
+                                                    last_name=last_name, defaults=defaults)
+
+        if locations is None:
+            locations = [self.getAlreadyCreatedSmashLocation()]
+        worker.locations.set(locations)
+        if languages is None:
+            languages = self.getAlreadyCreatedSmashLanguages()
+        worker.languages.set(languages)
+        worker.save()
+
+        self.alreadyCreatedWorkers.append(worker)
+
+        # create workerStudyRole
+        workerStudyRole, _ = WorkerStudyRole.objects.update_or_create(worker=worker,
+                                                                      study_id=GLOBAL_STUDY_ID, role=role)
+
+    # unit
+    def getSmashUnit(self):
+        return self.fake.word(ext_word_list=self.units)
+
+    # specialization
+    def getSmashSpecialization(self):
+        return self.fake.word(ext_word_list=self.specialists)
+
+    # languages
+    def createSmashLanguages(self):
+        '''
+        Returns a list of Language objects
+        '''
+        logger.info('creating Smash Languages...')
+        return [self.createSmashLanguage(name=name) for name in self.languages]
+
+    def createSmashLanguage(self, name=None, locale=None):
+        '''
+        Returns a Language object
+        '''
+        if name is None:
+            name = self.fake.word(ext_word_list=self.languages)
+
+        if locale is None:
+            if name in self.language_locale:
+                locale = self.language_locale[name]
+            else:
+                locale = DEFAULT_LOCALE_NAME
+
+        language, _ = Language.objects.update_or_create(
+            name=name, locale=locale)
+        self.alreadyCreatedLanguages[name] = language
+
+        if name in self.language_flags:
+            src = os.path.join(self.language_base_dir,
+                               self.language_flags[name])
+            basename = os.path.basename(src)
+            dst = os.path.join(MEDIA_ROOT, basename)
+            copyfile(src, dst)
+            # .save(basename, File(open(dst, 'rb'))) #SimpleUploadedFile(name=path, content=open(path, 'rb').read(), content_type='image/png')
+            language.image = basename
+            language.save()
+
+        return language
+
+    def getAlreadyCreatedSmashLanguages(self, max_n=3):
+        '''
+        Returns a list of Languages from the already created languages, 
+        if no languages are created, then the list is generated.
+
+        max_n: int: maximum number of languages to return
+
+        Returns a list of Language objects.
+        '''
+        if len(self.alreadyCreatedLanguages.keys()) == 0:
+            self.createSmashLanguages()
+
+        languages = choice(self.languages, max_n, p=self.languages_prob)
+        keys = set(languages)  # unique
+        return [self.alreadyCreatedLanguages[key] for key in keys]
+
+    # locations
+    def getAlreadyCreatedSmashLocation(self):
+        return self.random_element(self.alreadyCreatedLocations.values())
+
+    def createSmashLocations(self, n=5):
+        logger.info('creating Smash Locations...')
+        return [self.createSmashLocation() for _ in xrange(n)]
+
+    def createSmashLocation(self, city=None, color=None, prefix=None):
+        if city is None:
+            city = choice(self.cities, 1, p=self.city_prob)[0]
+
+        if color is None:
+            color = self.fake.hex_color()
+
+        if prefix is None:
+            prefix = city[:1]
+
+        location, _ = Location.objects.update_or_create(
+            name=city, color=color, prefix=prefix)
+
+        self.alreadyCreatedLocations[city] = location
+
+        return location
+
+    def getAllCreatedLocations(self):
+        return self.alreadyCreatedLocations.values()
+
+    def getAllCreatedLanguages(self):
+        return self.alreadyCreatedLanguages.values()
+
+if __name__ == "__main__":
+    if not os.path.exists(MEDIA_ROOT):
+        os.makedirs(MEDIA_ROOT)
+    fake = Faker()
+    fake.seed(4321)
+    fake.add_provider(smashProvider)
+    fake.createSmashFlyingTeams()
+    fake.createSmashItems()
+    fake.createSmashAppointmentTypes()
+    fake.createSmashRooms()
+    fake.createSmashLocations()
+    fake.createSmashLanguages()
+    fake.createSmashMailTemplates()
+    fake.createSmashCountries()
+
+    fake.createSmashWorkers()
+    fake.createSmashAvailabilities()
+    fake.createSmashStudySubjects()
+    fake.createSmashAppointments()
+    fake.createSmashWorker(first_name=u'System', last_name=u'Admin',
+                           email=u'carlos.vega@uni.lu', role=ROLE_CHOICES_TECHNICIAN, password='smashtest007',
+                           locations=fake.getAllCreatedLocations(), languages=fake.getAllCreatedLanguages())
diff --git a/smash/web/forms/study_forms.py b/smash/web/forms/study_forms.py
index 76c51a4288da0395d45e580bfa75ad5f689696e1..916f2316a4ff0c3f41a9f75840ea383a4d668bdb 100644
--- a/smash/web/forms/study_forms.py
+++ b/smash/web/forms/study_forms.py
@@ -1,8 +1,7 @@
 import logging
 
-from django.forms import ModelForm
-
-from web.models import Study, StudyNotificationParameters, StudyColumns
+from django.forms import ModelForm, ValidationError
+from web.models import Study, StudyNotificationParameters, StudyColumns, StudySubject
 
 logger = logging.getLogger(__name__)
 
@@ -12,6 +11,16 @@ class StudyEditForm(ModelForm):
     def __init__(self, *args, **kwargs):
         super(StudyEditForm, self).__init__(*args, **kwargs)
 
+    def clean_nd_number_study_subject_regex(self):
+        nd_number_study_subject_regex = self.cleaned_data.get(
+            'nd_number_study_subject_regex')
+
+        if StudySubject.check_nd_number_regex(nd_number_study_subject_regex) == False:
+            raise ValidationError(
+                'Please enter a valid nd_number_study_subject_regex regex.')
+
+        return nd_number_study_subject_regex
+
     class Meta:
         model = Study
         fields = '__all__'
@@ -21,7 +30,8 @@ class StudyEditForm(ModelForm):
 class StudyNotificationParametersEditForm(ModelForm):
 
     def __init__(self, *args, **kwargs):
-        super(StudyNotificationParametersEditForm, self).__init__(*args, **kwargs)
+        super(StudyNotificationParametersEditForm,
+              self).__init__(*args, **kwargs)
 
     class Meta:
         model = StudyNotificationParameters
diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py
index 44afb25786f47a3d43e6a7f56a9ea1387ccbdab6..76d4d7735119ae75280a3e7a42550d051879c632 100644
--- a/smash/web/forms/study_subject_forms.py
+++ b/smash/web/forms/study_subject_forms.py
@@ -197,7 +197,7 @@ def validate_subject_nd_number(self, cleaned_data):
     if self.study.columns.nd_number:
         nd_number = cleaned_data['nd_number']
         if nd_number != "":
-            if re.match('ND[0-9][0-9][0-9][0-9]', nd_number) is None:
+            if not self.study.check_nd_number(nd_number):
                 self.add_error('nd_number', "Invalid ND number")
             else:
                 subjects_from_db = StudySubject.objects.filter(nd_number=nd_number, study=self.study)
diff --git a/smash/web/migrations/0119_auto_20181024_1158.py b/smash/web/migrations/0119_auto_20181024_1158.py
new file mode 100644
index 0000000000000000000000000000000000000000..cab12fa337487854b82a8cf770efd1cda340dd36
--- /dev/null
+++ b/smash/web/migrations/0119_auto_20181024_1158.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2018-10-24 11:58
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0118_voucher_activity_type'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='studyvisitlist',
+            name='type',
+            field=models.CharField(choices=[(b'UNFINISHED', b'Unfinished visits'), (b'APPROACHING_WITHOUT_APPOINTMENTS', b'Approaching visits'), (b'APPROACHING_FOR_MAIL_CONTACT', b'Post mail for approaching visits'), (b'GENERIC', b'Generic visit list'), (b'MISSING_APPOINTMENTS', b'Visits with missing appointments'), (b'EXCEEDED_TIME', b'Exceeded visit time')], max_length=50, verbose_name=b'Type of list'),
+        ),
+        migrations.AlterField(
+            model_name='workerstudyrole',
+            name='role',
+            field=models.CharField(choices=[(b'DOCTOR', b'Doctor'), (b'NURSE', b'Nurse'), (b'PSYCHOLOGIST', b'Psychologist'), (b'TECHNICIAN', b'Technician'), (b'SECRETARY', b'Secretary'), (b'PROJECT MANAGER', b'Project Manager')], max_length=20, verbose_name=b'Role'),
+        ),
+    ]
diff --git a/smash/web/migrations/0120_study_nd_number_study_subject_regex.py b/smash/web/migrations/0120_study_nd_number_study_subject_regex.py
new file mode 100644
index 0000000000000000000000000000000000000000..9b4146edb8a1bcdfbf19a3144c4e9b932ee0beb6
--- /dev/null
+++ b/smash/web/migrations/0120_study_nd_number_study_subject_regex.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2018-10-24 12:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0119_auto_20181024_1158'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='study',
+            name='nd_number_study_subject_regex',
+            field=models.CharField(default=b'^ND\\d{4}$', help_text=b'Defines the regex to check the identification number used for each study subject.', max_length=255, verbose_name=b'Study Subject ND Number Regex'),
+        ),
+    ]
diff --git a/smash/web/migrations/0121_auto_20181024_1859.py b/smash/web/migrations/0121_auto_20181024_1859.py
new file mode 100644
index 0000000000000000000000000000000000000000..2e0b4654936fdae97a6ddc06809d3a89918a17d9
--- /dev/null
+++ b/smash/web/migrations/0121_auto_20181024_1859.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2018-10-24 18:59
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0120_study_nd_number_study_subject_regex'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='study',
+            name='nd_number_study_subject_regex',
+            field=models.CharField(default=b'^ND\\d{4}$', help_text=b'Defines the regex to check the ID used for each study subject. Keep in mind that this regex should be valid for all previous study subjects in the database.', max_length=255, verbose_name=b'Study Subject ND Number Regex'),
+        ),
+    ]
diff --git a/smash/web/migrations/0125_merge_20181025_0745.py b/smash/web/migrations/0125_merge_20181025_0745.py
new file mode 100644
index 0000000000000000000000000000000000000000..9559a6193bdeeb50cef4d5ee8d27af03c56dbac1
--- /dev/null
+++ b/smash/web/migrations/0125_merge_20181025_0745.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2018-10-25 07:45
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0124_auto_20181017_1532'),
+        ('web', '0121_auto_20181024_1859'),
+    ]
+
+    operations = [
+    ]
diff --git a/smash/web/models/study.py b/smash/web/models/study.py
index bfff2a6e067dd09fdb9803f4a261ec57c5412547..c7b90db332762b349b86edca75fe179f6cc620e7 100644
--- a/smash/web/models/study.py
+++ b/smash/web/models/study.py
@@ -3,13 +3,20 @@ from django.db import models
 
 from web.models import StudyColumns, StudyNotificationParameters
 
+import re
+
 
 class Study(models.Model):
+
     class Meta:
         app_label = 'web'
 
     name = models.CharField(max_length=255, verbose_name='Name')
 
+    nd_number_study_subject_regex = models.CharField(
+        max_length=255, verbose_name='Study Subject ND Number Regex', default=r'^ND\d{4}$',
+        help_text='Defines the regex to check the ID used for each study subject. Keep in mind that this regex should be valid for all previous study subjects in the database.')
+
     columns = models.OneToOneField(
         StudyColumns,
         on_delete=models.CASCADE,
@@ -24,6 +31,10 @@ class Study(models.Model):
         verbose_name="Auto create follow up visit"
     )
 
+    def check_nd_number(self, nd_number):
+        regex = re.compile(self.nd_number_study_subject_regex)
+        return regex.match(nd_number) is not None
+
     def __str__(self):
         return "%s" % self.name
 
@@ -44,4 +55,4 @@ class Study(models.Model):
         if len(study) > 0:
             return study[0]
         else:
-            return None
\ No newline at end of file
+            return None
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index 3854138909c993a2dbd98168dffd849b6fa48d97..e62425531401fd2e4808e49797649dbb87625ee4 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -1,5 +1,6 @@
 # coding=utf-8
 import logging
+import re
 from django.db import models
 
 from web.models import VoucherType, Appointment, Location, Visit
@@ -7,7 +8,9 @@ from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES, FILE_STORAG
 
 logger = logging.getLogger(__name__)
 
+
 class StudySubject(models.Model):
+
     class Meta:
         app_label = 'web'
 
@@ -18,7 +21,8 @@ class StudySubject(models.Model):
             visit.save()
 
     def finish_all_appointments(self):
-        appointments = Appointment.objects.filter(visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED)
+        appointments = Appointment.objects.filter(
+            visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED)
         for appointment in appointments:
             appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED
             appointment.save()
@@ -164,18 +168,28 @@ class StudySubject(models.Model):
         for part in parts:
             chunks = part.strip().split('-')
             if len(chunks) == 2:
-              letter, number = chunks
-              tupl = (letter, int(number))
+                letter, number = chunks
+                tupl = (letter, int(number))
             else:
-              logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format(len(chunks), self.screening_number))
-              tupl = (part.strip(), None)
+                logger.warn('There are {} chunks in some parts of this screening_number: |{}|.'.format(
+                    len(chunks), self.screening_number))
+                tupl = (part.strip(), None)
             if pattern is not None and pattern in part:
                 matches.append(tupl)
             else:
                 reminder.append(tupl)
 
         return matches + sorted(reminder, reverse=reverse)
-            
+
+    @staticmethod
+    def check_nd_number_regex(regex_str):
+        nd_numbers = StudySubject.objects.all().values_list('nd_number', flat=True)
+        regex = re.compile(regex_str)
+        for nd_number in nd_numbers:
+            if regex.match(nd_number) is None:
+                return False
+        return True
+
     def __str__(self):
         return "%s %s" % (self.subject.first_name, self.subject.last_name)
 
diff --git a/smash/web/templates/study/edit.html b/smash/web/templates/study/edit.html
index 54be5ce7bf8131024a775eea19c4ca053ee51e5c..e1e8d594d914cae156ec0bbac6bb0b134b9e488b 100644
--- a/smash/web/templates/study/edit.html
+++ b/smash/web/templates/study/edit.html
@@ -6,7 +6,12 @@
     {{ block.super }}
     <!-- DataTables -->
     <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
-
+    <style type="text/css">
+        .tooltip-inner {
+            max-width: 350px;
+            width: 350px; 
+        }
+    </style>
     {% include "includes/datepicker.css.html" %}
     {% include "includes/datetimepicker.css.html" %}
 {% endblock styles %}
@@ -45,6 +50,9 @@
                                     <div class="col-md-6 form-group  {% if field.errors %}has-error{% endif %}">
                                         <label for="{# TODO #}" class="col-sm-4 control-label">
                                             {{ field.label }}
+                                            {% if field.help_text %}
+                                                <i class="fa fa-info-circle" aria-hidden="true" data-toggle="tooltip" data-placement="bottom" title="{{field.help_text}}"></i>
+                                            {% endif %}
                                         </label>
 
                                         <div class="col-sm-8">
diff --git a/smash/web/tests/forms/test_study_forms.py b/smash/web/tests/forms/test_study_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..a150a83c181cee62dcf894648ed7d5bb797a0279
--- /dev/null
+++ b/smash/web/tests/forms/test_study_forms.py
@@ -0,0 +1,40 @@
+from django.test import TestCase
+from django.forms import ValidationError
+from web.tests.functions import create_study_subject
+from web.forms.study_forms import StudyEditForm
+from web.models.study import Study
+from web.models.study_subject import StudySubject
+
+class StudyFormTests(TestCase):
+
+    def test_study_default_regex(self):
+        # this will add a studysubject with a NDnumber
+        StudySubject.objects.all().delete()
+        create_study_subject(nd_number='ND0001')
+        form = StudyEditForm()
+        # set default regex
+        nd_number_study_subject_regex_default = Study._meta.get_field(
+            'nd_number_study_subject_regex').get_default()
+        form.cleaned_data = {
+            'nd_number_study_subject_regex': nd_number_study_subject_regex_default}
+        self.assertTrue(form.clean_nd_number_study_subject_regex()
+                        == nd_number_study_subject_regex_default)
+        # test wrong regex
+        form = StudyEditForm()
+        nd_number_study_subject_regex_default = r'^nd\d{5}$'
+        form.cleaned_data = {
+            'nd_number_study_subject_regex': nd_number_study_subject_regex_default}
+        self.assertRaises(
+            ValidationError, form.clean_nd_number_study_subject_regex)
+
+    def test_study_other_regex(self):
+        StudySubject.objects.all().delete()
+        # this will add a studysubject with a NDnumber
+        create_study_subject(nd_number='nd00001')
+        form = StudyEditForm()
+        # test new regex
+        nd_number_study_subject_regex_default = r'^nd\d{5}$'
+        form.cleaned_data = {
+            'nd_number_study_subject_regex': nd_number_study_subject_regex_default}
+        self.assertTrue(form.clean_nd_number_study_subject_regex()
+                        == nd_number_study_subject_regex_default)
diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py
index ed17deb7e769ffdb6125cb317302840c934b29e6..617756cbd2fa6b5530da8a9afcb0b6dd82b8c6b2 100644
--- a/smash/web/tests/functions.py
+++ b/smash/web/tests/functions.py
@@ -186,16 +186,22 @@ def create_subject():
     )
 
 
-def create_study_subject(subject_id=1, subject=None):
+def create_study_subject(subject_id=1, subject=None, nd_number='ND0001'):
     if subject is None:
         subject = create_subject()
-    return StudySubject.objects.create(
+    study_subject = StudySubject.objects.create(
         default_location=get_test_location(),
         type=SUBJECT_TYPE_CHOICES_CONTROL,
         screening_number="piotr's number" + str(subject_id),
         study=get_test_study(),
         subject=subject
     )
+    if nd_number is not None:
+        study_subject.nd_number = nd_number
+        study_subject.save()
+
+    return study_subject
+
 
 def create_study_subject_with_multiple_screening_numbers(subject_id=1, subject=None):
     if subject is None:
@@ -242,7 +248,8 @@ def create_worker(user=None, with_test_location=False):
     if with_test_location:
         worker.locations = [get_test_location()]
         worker.save()
-    WorkerStudyRole.objects.create(worker=worker, study_id=GLOBAL_STUDY_ID, role=ROLE_CHOICES_DOCTOR)
+    WorkerStudyRole.objects.create(
+        worker=worker, study_id=GLOBAL_STUDY_ID, role=ROLE_CHOICES_DOCTOR)
     return worker
 
 
@@ -255,7 +262,8 @@ def create_voucher_partner():
         unit="LCSB",
         phone_number="0123456789"
     )
-    WorkerStudyRole.objects.create(worker=worker, study_id=GLOBAL_STUDY_ID, role=WORKER_VOUCHER_PARTNER)
+    WorkerStudyRole.objects.create(
+        worker=worker, study_id=GLOBAL_STUDY_ID, role=WORKER_VOUCHER_PARTNER)
     return worker
 
 
@@ -358,10 +366,12 @@ def format_form_field(value):
 def prepare_test_redcap_connection():
     Language.objects.create(name="Finnish").save()
     Language.objects.create(name="Italian").save()
-    token_item = ConfigurationItem.objects.filter(type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0]
+    token_item = ConfigurationItem.objects.filter(
+        type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0]
     # noinspection SpellCheckingInspection
     token_item.value = "5C75EEC3DBDDA5218B6ACC0424B3F695"
     token_item.save()
-    url_item = ConfigurationItem.objects.filter(type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0]
+    url_item = ConfigurationItem.objects.filter(
+        type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0]
     url_item.value = "https://luxparktest.org/redcap/"
     url_item.save()
diff --git a/smash/web/tests/models/test_study.py b/smash/web/tests/models/test_study.py
index 9c9e02f77c08015bd59a4ef19149945caa5a0e6b..c535aa57a01becec96c39fb51ef4da443105de5a 100644
--- a/smash/web/tests/models/test_study.py
+++ b/smash/web/tests/models/test_study.py
@@ -1,14 +1,22 @@
 import logging
 
 from django.test import TestCase
-
-from web.tests.functions import create_study
+from django.forms import ValidationError
+from web.tests.functions import create_study, create_study_subject
+from web.forms.study_forms import StudyEditForm
+from web.models.study import Study
+from web.models.study_subject import StudySubject
 
 logger = logging.getLogger(__name__)
 
 
 class StudyTests(TestCase):
+
     def test_image_img(self):
         study = create_study()
-
         self.assertTrue(study.name in str(study))
+
+    def test_check_nd_number(self):
+        study = create_study()
+        # check the default regex
+        self.assertTrue(study.check_nd_number('ND0001'))
diff --git a/smash/web/tests/models/test_study_subject.py b/smash/web/tests/models/test_study_subject.py
index b70add8cee109783433fdba1e5056bf60b1235cd..f91762f35e0df2c664e7b69b00f0cb40dd45192d 100644
--- a/smash/web/tests/models/test_study_subject.py
+++ b/smash/web/tests/models/test_study_subject.py
@@ -2,41 +2,73 @@ from django.test import TestCase
 
 from web.models import Appointment
 from web.models import Visit
+from web.models import StudySubject, Study
 from web.tests.functions import create_study_subject, create_appointment, create_study_subject_with_multiple_screening_numbers
 from web.tests.functions import create_visit
 
 
 class SubjectModelTests(TestCase):
+
     def test_mark_as_resigned(self):
         subject = create_study_subject()
         visit = create_visit(subject)
         appointment = create_appointment(visit)
 
         subject.mark_as_resigned()
-        appointment_status = Appointment.objects.filter(id=appointment.id)[0].status
+        appointment_status = Appointment.objects.filter(id=appointment.id)[
+            0].status
         visit_finished = Visit.objects.filter(id=visit.id)[0].is_finished
 
         self.assertTrue(subject.resigned)
         self.assertTrue(visit_finished)
-        self.assertEquals(Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status)
+        self.assertEquals(
+            Appointment.APPOINTMENT_STATUS_CANCELLED, appointment_status)
+
+    def test_check_nd_number_regex(self):
+        # delete everything
+        StudySubject.objects.all().delete()
+        # get default regex
+        nd_number_study_subject_regex_default = Study._meta.get_field(
+            'nd_number_study_subject_regex').get_default()
+        self.assertTrue(StudySubject.check_nd_number_regex(
+            nd_number_study_subject_regex_default))
+        # this will add a studysubject with a NDnumber
+        study_subject = create_study_subject(nd_number='ND0001')
+
+        self.assertTrue(StudySubject.check_nd_number_regex(
+            nd_number_study_subject_regex_default))
+        # delete everything
+        StudySubject.objects.all().delete()
+        # this will add a studysubject with a NDnumber
+        create_study_subject(nd_number='ND00001')
+        self.assertFalse(StudySubject.check_nd_number_regex(
+            nd_number_study_subject_regex_default))
 
     def test_sort_matched_screening_first(self):
 
         def create_result(phrase, subject_id=1):
             phrase = phrase.format(subject_id=subject_id)
             phrase = phrase.split(';')
-            for i,r in enumerate(phrase):
+            for i, r in enumerate(phrase):
                 letter, num = phrase[i].strip().split('-')
                 phrase[i] = (letter, int(num))
             return phrase
 
-        subject = create_study_subject_with_multiple_screening_numbers(subject_id=1)
-        self.assertEqual(subject.sort_matched_screening_first('L'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first('L-00'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first('E'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first('-'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first(''), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first('001'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first('00'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
-        self.assertEqual(subject.sort_matched_screening_first('potato'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
-        
\ No newline at end of file
+        subject = create_study_subject_with_multiple_screening_numbers(
+            subject_id=1)
+        self.assertEqual(subject.sort_matched_screening_first('L'), create_result(
+            'L-00{subject_id}; E-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first(
+            'L-00'), create_result('L-00{subject_id}; E-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first('E'), create_result(
+            'E-00{subject_id}; L-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first(
+            '-'), create_result('E-00{subject_id}; L-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first(''), create_result(
+            'E-00{subject_id}; L-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first('001'), create_result(
+            'E-00{subject_id}; L-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first('00'), create_result(
+            'E-00{subject_id}; L-00{subject_id}', subject_id=1))
+        self.assertEqual(subject.sort_matched_screening_first('potato'), create_result(
+            'E-00{subject_id}; L-00{subject_id}', subject_id=1))
diff --git a/smash/web/tests/view/test_appointments.py b/smash/web/tests/view/test_appointments.py
index e17bb743efcab2bf5f7b467e2786592f7ed1c566..4acf168d587ae17c78e9748b54e56fb6e6220d10 100644
--- a/smash/web/tests/view/test_appointments.py
+++ b/smash/web/tests/view/test_appointments.py
@@ -137,7 +137,7 @@ class AppointmentsViewTests(LoggedInTestCase):
         self.assertEqual(Appointment.APPOINTMENT_STATUS_FINISHED, appointment_result.status)
 
     def test_save_appointments_edit_with_invalid_nd_number(self):
-        subject = create_study_subject()
+        subject = create_study_subject(nd_number=None)
         visit = create_visit(subject)
         appointment = create_appointment(visit, get_today_midnight_date())
 
diff --git a/smash/web/tests/view/test_worker.py b/smash/web/tests/view/test_worker.py
index 9c49f8d7a80bd1a7053fb468220e92204e43cc5c..bead17b6e0286145621d9418bdc8461242ea974b 100644
--- a/smash/web/tests/view/test_worker.py
+++ b/smash/web/tests/view/test_worker.py
@@ -5,7 +5,7 @@ from django_common.auth_backends import User
 
 from web.forms import WorkerForm
 from web.models import Worker
-from web.models.worker_study_role import WORKER_STAFF, ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER
+from web.models.worker_study_role import WORKER_STAFF, ROLE_CHOICES_DOCTOR, WORKER_HEALTH_PARTNER, WORKER_VOUCHER_PARTNER
 from web.tests import create_worker
 from web.tests.functions import create_language, create_location, create_availability, format_form_field
 from .. import LoggedInTestCase
@@ -16,10 +16,18 @@ logger = logging.getLogger(__name__)
 class WorkerViewTests(LoggedInTestCase):
     def test_render_workers_list_request(self):
         create_worker()
-
         response = self.client.get(reverse('web.views.workers'))
         self.assertEqual(response.status_code, 200)
 
+    def test_render_worker_type_request(self):
+        create_worker()
+        response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_STAFF}))
+        self.assertEqual(response.status_code, 200)
+        response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_HEALTH_PARTNER}))
+        self.assertEqual(response.status_code, 200)
+        response = self.client.get(reverse('web.views.workers', kwargs={'worker_type': WORKER_VOUCHER_PARTNER}))
+        self.assertEqual(response.status_code, 200)
+
     def test_render_add_worker_request(self):
         response = self.client.get(reverse('web.views.worker_add', kwargs={'worker_type': WORKER_STAFF}))
         self.assertEqual(response.status_code, 200)
diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py
index bc782e45a686222e32dab5465f3cbff5f158cd2b..ee45e0d173bc980f05d866df56cb70387ae05830 100644
--- a/smash/web/views/appointment.py
+++ b/smash/web/views/appointment.py
@@ -6,12 +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.constants import GLOBAL_STUDY_ID
 from web.models.appointment_list import APPOINTMENT_LIST_APPROACHING, APPOINTMENT_LIST_GENERIC, \
     APPOINTMENT_LIST_UNFINISHED
 from . import wrap_response
 from web.forms import AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, SubjectEditForm, \
     StudySubjectEditForm
-from ..models import Appointment, StudySubject, MailTemplate, Visit
+from ..models import Appointment, StudySubject, MailTemplate, Visit, Study
 
 logger = logging.getLogger(__name__)
 
@@ -73,6 +74,7 @@ def appointment_edit(request, id):
     study_subject_form = None
     subject_form = None
     contact_attempts = None
+    study = Study.get_by_id(GLOBAL_STUDY_ID)
 
     if request.method == 'POST':
         appointment_form = AppointmentEditForm(request.POST,
@@ -101,7 +103,7 @@ def appointment_edit(request, id):
                                                status=Appointment.APPOINTMENT_STATUS_FINISHED).count() == 0:
                 adjust_date = True
             if appointment_form.cleaned_data["status"] == Appointment.APPOINTMENT_STATUS_FINISHED:
-                if re.match('ND[0-9][0-9][0-9][0-9]', study_subject_form.cleaned_data["nd_number"]) is None:
+                if not study.check_nd_number(study_subject_form.cleaned_data["nd_number"]):
                     study_subject_form.add_error('nd_number', ValidationError("invalid ND number"))
                     is_valid_form = False
         if is_valid_form: