diff --git a/smash/smash/settings.py b/smash/smash/settings.py
index c11fe16accedc21a7dcdf46f168e4305be7ec5ea..14f7a7ff96dc662eadb5b2216029619d0f16cc28 100644
--- a/smash/smash/settings.py
+++ b/smash/smash/settings.py
@@ -76,6 +76,7 @@ CRON_CLASSES = [
     'web.views.kit.KitRequestEmailSendJob',
     'web.redcap_connector.RedCapRefreshJob',
     'web.views.voucher.ExpireVouchersJob',
+    'web.importer.importer_cron_job.ImporterCronJob'
 ]
 
 # Password validation
diff --git a/smash/web/importer/__init__.py b/smash/web/importer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..eda42f3accab2707cf3151273f71db973c51f267
--- /dev/null
+++ b/smash/web/importer/__init__.py
@@ -0,0 +1,8 @@
+from csv_subject_import_reader import CsvSubjectImportReader
+from exporter import Exporter
+from exporter_cron_job import ExporterCronJob
+from importer import Importer
+from importer_cron_job import ImporterCronJob
+from subject_import_reader import SubjectImportReader
+
+__all__ = [Importer, SubjectImportReader, CsvSubjectImportReader, ImporterCronJob, Exporter, ExporterCronJob]
diff --git a/smash/web/importer/csv_subject_import_reader.py b/smash/web/importer/csv_subject_import_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..187b59aba9d98798f8630054828256aa3b80b07f
--- /dev/null
+++ b/smash/web/importer/csv_subject_import_reader.py
@@ -0,0 +1,74 @@
+import csv
+import datetime
+import logging
+
+from subject_import_reader import SubjectImportReader
+from web.models import StudySubject, Subject, Study
+from web.models.constants import GLOBAL_STUDY_ID
+
+CSV_DATE_FORMAT = "%d-%m-%Y"
+
+logger = logging.getLogger(__name__)
+
+
+class CsvSubjectImportReader(SubjectImportReader):
+    def __init__(self):
+        self.study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+    def load_data(self, filename):
+        study_subjects = []
+        with open(filename) as csv_file:
+            reader = csv.reader(csv_file, delimiter=',')
+            headers = next(reader, None)
+            for row in reader:
+                subject = Subject()
+                study_subject = StudySubject()
+                study_subject.subject = subject
+                study_subject.study = self.study
+                for header, value in zip(headers, row):
+                    self.add_data(study_subject, header, value)
+                if study_subject.nd_number is None or study_subject.nd_number == "":
+                    study_subject.nd_number = study_subject.screening_number
+                study_subjects.append(study_subject)
+        return study_subjects
+
+    def add_data(self, study_subject, column_name, value):
+        # type: (StudySubject, str, str) ->  None
+        if column_name == "first_name":
+            study_subject.subject.first_name = self.get_new_value(study_subject.subject.first_name, column_name, value)
+        elif column_name == "last_name":
+            study_subject.subject.last_name = self.get_new_value(study_subject.subject.last_name, column_name, value)
+        elif column_name == "participant_id":
+            study_subject.screening_number = self.get_new_value(study_subject.screening_number, column_name, value)
+        elif column_name == "date_born":
+            study_subject.subject.date_born = self.get_new_date_value(study_subject.subject.date_born, column_name,
+                                                                      value)
+        else:
+            logger.warn("Don't know how to handle column " + column_name + " with data " + value)
+
+    def get_new_value(self, old_value, column_name, new_value):
+        # type: (unicode,unicode,unicode) ->  unicode
+        if old_value is None or old_value == "":
+            return new_value
+        if new_value is None or new_value == "":
+            return old_value
+        logger.warn(
+            "Contradicting entries in csv file for column: " + column_name + "(" + new_value + "," + old_value +
+            "). Latest value will be used")
+        return new_value
+
+    def get_new_date_value(self, old_value, column_name, new_value):
+        # type: (datetime,unicode,unicode) ->  datetime
+        if old_value is None or old_value == "":
+            try:
+                result = datetime.datetime.strptime(new_value, CSV_DATE_FORMAT)
+            except ValueError:
+                logger.warn("Invalid date: " + new_value)
+                result = old_value
+            return result
+        if new_value is None or new_value == "":
+            return old_value
+        logger.warn(
+            "Contradicting entries in csv file for column: " + column_name + "(" + new_value + "," + old_value +
+            "). Latest value will be used")
+        return datetime.datetime.strptime(new_value, CSV_DATE_FORMAT)
diff --git a/smash/web/importer/exporter.py b/smash/web/importer/exporter.py
new file mode 100644
index 0000000000000000000000000000000000000000..99b72d79a94443e3f04f4a7ad69f2c859da7b339
--- /dev/null
+++ b/smash/web/importer/exporter.py
@@ -0,0 +1,50 @@
+# coding=utf-8
+import csv
+import logging
+
+from warning_counter import MsgCounterHandler
+from web.models import StudySubject
+
+logger = logging.getLogger(__name__)
+
+
+class Exporter(object):
+    def __init__(self, filename):
+        # type: (str) -> None
+        self.filename = filename
+        self.exported_count = 0
+        self.warning_count = 0
+
+    def execute(self):
+        self.exported_count = 0
+        self.warning_count = 0
+
+        warning_counter = MsgCounterHandler()
+        logging.getLogger('').addHandler(warning_counter)
+
+        with open(self.filename, 'w') as csv_file:
+            data = self.get_subjects_as_array()
+            writer = csv.writer(csv_file, quotechar=str(u'"'), quoting=csv.QUOTE_ALL)
+            for row in data:
+                writer.writerow([s.encode("utf-8") for s in row])
+                self.exported_count += 1
+
+        if "WARNING" in warning_counter.level2count:
+            self.warning_count = warning_counter.level2count["WARNING"]
+        logging.getLogger('').removeHandler(warning_counter)
+
+    def get_summary(self):
+        result = "<p>Number of entries: <b>" + str(self.exported_count) + "</b></p>"
+        style = ''
+        if self.warning_count > 0:
+            style = ' color="brown" '
+        result += "<p><font " + style + ">Number of raised warnings: <b>" + str(self.warning_count) + "</b></font></p>"
+
+        return result
+
+    def get_subjects_as_array(self):
+        result = []
+        study_subjects = StudySubject.objects.filter(excluded=True)
+        for study_subject in study_subjects:
+            result.append([study_subject.nd_number])
+        return result
diff --git a/smash/web/importer/exporter_cron_job.py b/smash/web/importer/exporter_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..90ce95a604070e873332ec0c8610180808694ece
--- /dev/null
+++ b/smash/web/importer/exporter_cron_job.py
@@ -0,0 +1,46 @@
+# coding=utf-8
+import logging
+import traceback
+
+import timeout_decorator
+from django.conf import settings
+from django_cron import CronJobBase, Schedule
+
+from exporter import Exporter
+from web.models.constants import CRON_JOB_TIMEOUT
+from ..smash_email import EmailSender
+
+logger = logging.getLogger(__name__)
+
+
+class ExporterCronJob(CronJobBase):
+    RUN_EVERY_MINUTES = 60 * 24
+    schedule = Schedule(run_every_mins=RUN_EVERY_MINUTES)
+    code = 'web.import_daily_job'  # a unique code
+
+    @timeout_decorator.timeout(CRON_JOB_TIMEOUT)
+    def do(self):
+        email_title = "Daily export"
+        email_recipients = getattr(settings, "DEFAULT_FROM_EMAIL", None)
+
+        filename = getattr(settings, "DAILY_EXPORT_FILE", None)
+
+        if filename is None:
+            logger.info("Exporting subjects skipped. File not defined ")
+            return "export file not defined"
+        logger.info("Exporting subjects from file: " + filename)
+        try:
+            exporter = Exporter(settings.DAILY_EXPORT_FILE)
+            exporter.execute()
+            email_body = exporter.get_summary()
+            EmailSender().send_email(email_title,
+                                     "<h3>Data was successfully exported to file: " + filename + "</h3>" + email_body,
+                                     email_recipients)
+            return "export is successful"
+
+        except:
+            tb = traceback.format_exc()
+            EmailSender().send_email(email_title,
+                                     "<h3><font color='red'>There was a problem with exporting data to file: " + filename + "</font></h3><pre>" + tb + "</pre>",
+                                     email_recipients)
+            return "export crashed"
diff --git a/smash/web/importer/importer.py b/smash/web/importer/importer.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c7702f0de38fb9a29156e87b207b382f7a7b1ff
--- /dev/null
+++ b/smash/web/importer/importer.py
@@ -0,0 +1,161 @@
+# coding=utf-8
+import logging
+import sys
+import traceback
+
+from django.conf import settings
+from django.contrib.auth.models import User
+
+from subject_import_reader import SubjectImportReader
+from warning_counter import MsgCounterHandler
+from web.models import StudySubject, Subject, Provenance, Worker
+from web.models.constants import GLOBAL_STUDY_ID
+
+logger = logging.getLogger(__name__)
+
+
+class Importer(object):
+    def __init__(self, filename, reader):
+        # type: (str, SubjectImportReader) -> None
+        self.filename = filename
+        self.reader = reader
+        self.added_count = 0
+        self.problematic_count = 0
+        self.merged_count = 0
+        self.warning_count = 0
+        self.study_subjects = []
+
+        self.importer_user = None
+
+        importer_user_name = getattr(settings, "IMPORTER_USER", None)
+        if importer_user_name is not None:
+            user = User.objects.filter(username=importer_user_name)
+            if user is None:
+                logger.warn("User does not exist: " + importer_user_name)
+            else:
+                self.importer_user = Worker.objects.filter(user=user)
+
+    def execute(self):
+        self.added_count = 0
+        self.problematic_count = 0
+        self.merged_count = 0
+        self.warning_count = 0
+
+        warning_counter = MsgCounterHandler()
+        logging.getLogger('').addHandler(warning_counter)
+
+        self.study_subjects = self.reader.load_data(self.filename)
+        for study_subject in self.study_subjects:
+            try:
+                if study_subject.study is None:
+                    self.problematic_count += 1
+                    logger.warn("Empty study found. Ignoring")
+                    continue
+                elif study_subject.study.id != GLOBAL_STUDY_ID:
+                    self.problematic_count += 1
+                    logger.warn("Empty study found. Ignoring: " + study_subject.study.id)
+                    continue
+                else:
+                    self.import_study_subject(study_subject)
+            except:
+                self.problematic_count += 1
+                traceback.print_exc(file=sys.stdout)
+                logger.error("Problem with importing study subject: " + study_subject.screening_number)
+        if "WARNING" in warning_counter.level2count:
+            self.warning_count = warning_counter.level2count["WARNING"]
+        logging.getLogger('').removeHandler(warning_counter)
+
+    def import_study_subject(self, study_subject):
+        # type: (StudySubject) -> None
+        db_study_subjects = StudySubject.objects.filter(screening_number=study_subject.screening_number)
+        if db_study_subjects.count() > 0:
+            db_study_subject = db_study_subjects.first()
+            for field in Subject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject.subject, field.name)
+                    if new_value is not None and new_value != "":
+                        old_value = getattr(db_study_subject.subject, field.name)
+                        description = '{} changed from "{}" to "{}"'.format(field, old_value, new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=db_study_subject.subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value=old_value,
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        setattr(db_study_subject.subject, field.name, new_value)
+                        p.save()
+
+            db_study_subject.subject.save()
+
+            for field in StudySubject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject, field.name)
+                    if new_value is not None and new_value != "":
+                        old_value = getattr(db_study_subject, field.name)
+                        description = '{} changed from "{}" to "{}"'.format(field, old_value, new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=db_study_subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value=old_value,
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        setattr(db_study_subject, field.name, new_value)
+                        p.save()
+            db_study_subject.save()
+            self.merged_count += 1
+        else:
+            study_subject.subject.save()
+            study_subject.subject = Subject.objects.filter(id=study_subject.subject.id)[0]
+            study_subject.save()
+
+            for field in Subject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject.subject, field.name)
+                    if new_value is not None and new_value != "":
+                        description = '{} changed from "{}" to "{}"'.format(field, '', new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=study_subject.subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value='',
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        p.save()
+
+            for field in StudySubject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject, field.name)
+                    if new_value is not None and new_value != "":
+                        description = '{} changed from "{}" to "{}"'.format(field, '', new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=study_subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value='',
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        p.save()
+
+            self.added_count += 1
+
+    def get_summary(self):
+        result = "<p>Number of entries: <b>" + str(len(self.study_subjects)) + "</b></p>" + \
+                 "<p>Number of successfully added entries: <b>" + str(self.added_count) + "</b></p>" + \
+                 "<p>Number of successfully merged entries: <b>" + str(self.merged_count) + "</b></p>"
+        style = ''
+        if self.problematic_count > 0:
+            style = ' color="red" '
+        result += "<p><font " + style + ">Number of problematic entries: <b>" + str(
+            self.problematic_count) + "</b></font></p>"
+        style = ''
+        if self.warning_count > 0:
+            style = ' color="brown" '
+        result += "<p><font " + style + ">Number of raised warnings: <b>" + str(self.warning_count) + "</b></font></p>"
+
+        return result
diff --git a/smash/web/importer/importer_cron_job.py b/smash/web/importer/importer_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfb75d105c4d0db0daaea06034fa1a82946b182b
--- /dev/null
+++ b/smash/web/importer/importer_cron_job.py
@@ -0,0 +1,61 @@
+# coding=utf-8
+import datetime
+import logging
+import os
+import os.path
+import traceback
+
+import timeout_decorator
+from django.conf import settings
+from django_cron import CronJobBase, Schedule
+
+from csv_subject_import_reader import CsvSubjectImportReader
+from importer import Importer
+from web.models.constants import CRON_JOB_TIMEOUT
+from ..smash_email import EmailSender
+
+logger = logging.getLogger(__name__)
+
+
+class ImporterCronJob(CronJobBase):
+    RUN_EVERY_MINUTES = 60 * 24
+    schedule = Schedule(run_every_mins=RUN_EVERY_MINUTES)
+    code = 'web.import_daily_job'  # a unique code
+
+    @timeout_decorator.timeout(CRON_JOB_TIMEOUT)
+    def do(self):
+        email_title = "Daily import"
+        email_recipients = getattr(settings, "DEFAULT_FROM_EMAIL", None)
+
+        filename = getattr(settings, "DAILY_IMPORT_FILE", None)
+
+        if filename is None:
+            logger.info("Importing subjects skipped. File not defined ")
+            return "import file not defined"
+        logger.info("Importing subjects from file: " + filename)
+        if not os.path.isfile(filename):
+            EmailSender().send_email(email_title,
+                                     "<h3><font color='red'>File with imported data is not available in the system: " + filename + "</font></h3>",
+                                     email_recipients)
+            return "import file not found"
+        try:
+            importer = Importer(settings.DAILY_IMPORT_FILE, CsvSubjectImportReader())
+            importer.execute()
+            email_body = importer.get_summary()
+            EmailSender().send_email(email_title,
+                                     "<h3>Data was successfully imported from file: " + filename + "</h3>" + email_body,
+                                     email_recipients)
+            self.backup_file(filename)
+            return "import is successful"
+
+        except:
+            tb = traceback.format_exc()
+            EmailSender().send_email(email_title,
+                                     "<h3><font color='red'>There was a problem with importing data from file: " + filename + "</font></h3><pre>" + tb + "</pre>",
+                                     email_recipients)
+            return "import crashed"
+
+    def backup_file(self, filename):
+        new_file = filename + "-" + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M") + ".bac"
+        os.rename(filename, new_file)
+        return
diff --git a/smash/web/importer/subject_import_reader.py b/smash/web/importer/subject_import_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bc7f898ea24a1af7e99b32bca1c8a095635a38d
--- /dev/null
+++ b/smash/web/importer/subject_import_reader.py
@@ -0,0 +1,7 @@
+from web.models.study_subject import StudySubject
+
+
+class SubjectImportReader:
+    def load_data(self, filename):
+        # type: (str) ->  List [StudySubject]
+        pass
diff --git a/smash/web/importer/warning_counter.py b/smash/web/importer/warning_counter.py
new file mode 100644
index 0000000000000000000000000000000000000000..8500d6a34303f5e2368f536ccd9c206069174eae
--- /dev/null
+++ b/smash/web/importer/warning_counter.py
@@ -0,0 +1,15 @@
+import logging
+
+class MsgCounterHandler(logging.Handler):
+    level2count = None
+
+    def __init__(self, *args, **kwargs):
+        super(MsgCounterHandler, self).__init__(*args, **kwargs)
+        self.level2count = {}
+
+    def emit(self, record):
+        l = record.levelname
+        if l not in self.level2count:
+            self.level2count[l] = 0
+        print(l)
+        self.level2count[l] += 1
diff --git a/smash/web/tests/data/import.csv b/smash/web/tests/data/import.csv
new file mode 100644
index 0000000000000000000000000000000000000000..b13e437a14950691eb4f1daaa7b9cc2a609f8843
--- /dev/null
+++ b/smash/web/tests/data/import.csv
@@ -0,0 +1,2 @@
+first_name,last_name,participant_id,date_born
+Piotr,Gawron,Cov-000001,01-02-2020
\ No newline at end of file
diff --git a/smash/web/tests/data/import_date_of_birth.csv b/smash/web/tests/data/import_date_of_birth.csv
new file mode 100644
index 0000000000000000000000000000000000000000..43d9adb8ac452c865ffcbdb96446f22847873c61
--- /dev/null
+++ b/smash/web/tests/data/import_date_of_birth.csv
@@ -0,0 +1,4 @@
+first_name,last_name,participant_id,date_born
+Piotr,Gawron,Cov-000001,
+Piotr,Gawron,Cov-000002,invalid
+Piotr,Gawron,Cov-000003,2222-20-20
\ No newline at end of file
diff --git a/smash/web/tests/importer/__init__.py b/smash/web/tests/importer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/smash/web/tests/importer/mock_reader.py b/smash/web/tests/importer/mock_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5aedb09dd658512fcbcfbe8a80913e6f144026f
--- /dev/null
+++ b/smash/web/tests/importer/mock_reader.py
@@ -0,0 +1,13 @@
+import logging
+
+from web.importer.subject_import_reader import SubjectImportReader
+
+logger = logging.getLogger(__name__)
+
+
+class MockReader(SubjectImportReader):
+    def __init__(self, study_subjects):
+        self.study_subjects = study_subjects
+
+    def load_data(self, filename):
+        return self.study_subjects
diff --git a/smash/web/tests/importer/test_csv_subject_import_reader.py b/smash/web/tests/importer/test_csv_subject_import_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..a80f323469577c13837ee6bf83c6ed1baebd004c
--- /dev/null
+++ b/smash/web/tests/importer/test_csv_subject_import_reader.py
@@ -0,0 +1,37 @@
+# coding=utf-8
+
+import logging
+
+from django.test import TestCase
+
+from web.importer import CsvSubjectImportReader
+from web.tests.functions import get_resource_path
+
+logger = logging.getLogger(__name__)
+
+
+class TestCsvReader(TestCase):
+
+    def test_load_data(self):
+        filename = get_resource_path('import.csv')
+        study_subjects = CsvSubjectImportReader().load_data(filename)
+        self.assertEqual(1, len(study_subjects))
+        study_subject = study_subjects[0]
+        self.assertEqual("Piotr", study_subject.subject.first_name)
+        self.assertEqual("Gawron", study_subject.subject.last_name)
+        self.assertEqual("Cov-000001", study_subject.screening_number)
+        self.assertEqual("Cov-000001", study_subject.nd_number)
+
+        self.assertEqual(1, study_subject.subject.date_born.day)
+        self.assertEqual(2, study_subject.subject.date_born.month)
+        self.assertEqual(2020, study_subject.subject.date_born.year)
+
+        self.assertIsNotNone(study_subject.study)
+
+    def test_load_problematic_dates(self):
+        filename = get_resource_path('import_date_of_birth.csv')
+        study_subjects = CsvSubjectImportReader().load_data(filename)
+        self.assertEqual(3, len(study_subjects))
+        self.assertIsNone(study_subjects[0].subject.date_born)
+        self.assertIsNone(study_subjects[1].subject.date_born)
+        self.assertIsNone(study_subjects[2].subject.date_born)
diff --git a/smash/web/tests/importer/test_exporter.py b/smash/web/tests/importer/test_exporter.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a552443b9ba73818783f213a5af0ced1b50a890
--- /dev/null
+++ b/smash/web/tests/importer/test_exporter.py
@@ -0,0 +1,40 @@
+# coding=utf-8
+
+import datetime
+import logging
+
+from django.test import TestCase
+
+from web.tests.functions import create_study_subject
+from web.importer import Exporter
+from web.models import Subject, StudySubject, Study, Provenance
+from web.models.constants import GLOBAL_STUDY_ID
+
+logger = logging.getLogger(__name__)
+
+
+class TestExporter(TestCase):
+
+    def setUp(self):
+        self.study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+    def test_export_not_excluded(self):
+        create_study_subject()
+
+        exporter = Exporter(filename="empty.csv")
+        exporter.execute()
+
+        self.assertEqual(0, exporter.exported_count)
+        self.assertEqual(0, exporter.warning_count)
+
+    def test_export_excluded(self):
+        subject = create_study_subject()
+        subject.excluded=True
+        subject.save()
+
+        exporter = Exporter(filename="empty.csv")
+        exporter.execute()
+
+        self.assertEqual(1, exporter.exported_count)
+        self.assertEqual(0, exporter.warning_count)
+
diff --git a/smash/web/tests/importer/test_exporter_cron_job.py b/smash/web/tests/importer/test_exporter_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1b44a37fbb2eec72ddceeef88d43d2928f689c4
--- /dev/null
+++ b/smash/web/tests/importer/test_exporter_cron_job.py
@@ -0,0 +1,45 @@
+# coding=utf-8
+
+import logging
+import tempfile
+
+from django.conf import settings
+from django.test import TestCase
+
+from web.importer import ExporterCronJob
+
+logger = logging.getLogger(__name__)
+from django.core import mail
+from django_cron.models import CronJobLog
+
+
+class TestCronJobExporter(TestCase):
+
+    def setUp(self):
+        setattr(settings, "DAILY_EXPORT_FILE", None)
+
+    def tearDown(self):
+        setattr(settings, "DAILY_EXPORT_FILE", None)
+
+    def test_import_without_configuration(self):
+        CronJobLog.objects.all().delete()
+
+        job = ExporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("export file not defined", status)
+        self.assertEqual(0, len(mail.outbox))
+
+    def test_import(self):
+        new_file, tmp = tempfile.mkstemp()
+
+        setattr(settings, "DAILY_EXPORT_FILE", tmp)
+        CronJobLog.objects.all().delete()
+
+        job = ExporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("export is successful", status)
+        self.assertEqual(1, len(mail.outbox))
diff --git a/smash/web/tests/importer/test_importer.py b/smash/web/tests/importer/test_importer.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5e95a1ea900a4a1742abece15e00cfea13160d2
--- /dev/null
+++ b/smash/web/tests/importer/test_importer.py
@@ -0,0 +1,110 @@
+# coding=utf-8
+
+import datetime
+import logging
+
+from django.test import TestCase
+
+from mock_reader import MockReader
+from web.tests.functions import create_study_subject
+from web.importer import Importer
+from web.models import Subject, StudySubject, Study, Provenance
+from web.models.constants import GLOBAL_STUDY_ID
+
+logger = logging.getLogger(__name__)
+
+
+class TestImporter(TestCase):
+
+    def setUp(self):
+        self.study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+    def test_import_new_subject(self):
+        study_subjects = []
+        subject = Subject()
+        subject.first_name = "Piotr"
+        subject.last_name = "Gawron"
+        study_subject = StudySubject()
+        study_subject.screening_number = "Cov-123456"
+        study_subject.subject = subject
+        study_subject.study = self.study
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        subject_counter = Subject.objects.count()
+        study_subject_counter = StudySubject.objects.count()
+        provenance_counter = Provenance.objects.count()
+        importer.execute()
+
+        self.assertEqual(subject_counter + 1, Subject.objects.count())
+        self.assertEqual(study_subject_counter + 1, StudySubject.objects.count())
+        self.assertNotEqual(provenance_counter, Provenance.objects.count())
+
+        self.assertEqual(1, importer.added_count)
+        self.assertEqual(0, importer.problematic_count)
+        self.assertEqual(0, importer.warning_count)
+
+    def test_import_invalid(self):
+        study_subjects = []
+        study_subject = StudySubject()
+        study_subject.screening_number = "Cov-123456"
+        study_subject.study = self.study
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        study_subject_counter = StudySubject.objects.count()
+        importer.execute()
+
+        self.assertEqual(study_subject_counter, StudySubject.objects.count())
+
+        self.assertEqual(0, importer.added_count)
+        self.assertEqual(1, importer.problematic_count)
+
+    def test_import_no_study(self):
+        study_subjects = []
+        study_subject = StudySubject()
+        study_subject.screening_number = "Cov-123456"
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        study_subject_counter = StudySubject.objects.count()
+        importer.execute()
+
+        self.assertEqual(study_subject_counter, StudySubject.objects.count())
+
+        self.assertEqual(0, importer.added_count)
+        self.assertEqual(1, importer.problematic_count)
+
+    def test_import_merge_subject(self):
+        existing_study_subject = create_study_subject()
+        study_subjects = []
+        subject = Subject()
+        subject.first_name = "XYZ"
+        subject.last_name = "AAA"
+        subject.date_born = datetime.datetime.now()
+        study_subject = StudySubject()
+        study_subject.screening_number = existing_study_subject.screening_number
+        study_subject.subject = subject
+        study_subject.study = self.study
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        provenance_counter = Provenance.objects.count()
+        subject_counter = Subject.objects.count()
+        study_subject_counter = StudySubject.objects.count()
+        importer.execute()
+
+        self.assertEqual(subject_counter, Subject.objects.count())
+        self.assertEqual(study_subject_counter, StudySubject.objects.count())
+        self.assertNotEqual(provenance_counter, Provenance.objects.count())
+
+        self.assertEqual(0, importer.added_count)
+        self.assertEqual(1, importer.merged_count)
+        self.assertEqual(0, importer.problematic_count)
+        self.assertEqual(0, importer.warning_count)
+
+        existing_study_subject = StudySubject.objects.filter(id=existing_study_subject.id)[0]
+        self.assertEquals(existing_study_subject.subject.first_name, subject.first_name)
+        self.assertEquals(existing_study_subject.subject.last_name, subject.last_name)
+        self.assertEquals(existing_study_subject.subject.date_born.strftime("%Y-%m-%d"),
+                          subject.date_born.strftime("%Y-%m-%d"))
diff --git a/smash/web/tests/importer/test_importer_cron_job.py b/smash/web/tests/importer/test_importer_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..0eec9f6acb0969b11f703639ee961f665b71ad13
--- /dev/null
+++ b/smash/web/tests/importer/test_importer_cron_job.py
@@ -0,0 +1,46 @@
+# coding=utf-8
+
+import logging
+import tempfile
+from shutil import copyfile
+
+from django.conf import settings
+from django.test import TestCase
+
+from web.importer import ImporterCronJob
+from web.tests.functions import get_resource_path
+
+logger = logging.getLogger(__name__)
+from django.core import mail
+from django_cron.models import CronJobLog
+
+
+class TestCronJobImporter(TestCase):
+
+    def tearDown(self):
+        setattr(settings, "DAILY_IMPORT_FILE", None)
+
+    def test_import_without_configuration(self):
+        CronJobLog.objects.all().delete()
+
+        job = ImporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("import file not defined", status)
+        self.assertEqual(0, len(mail.outbox))
+
+    def test_import(self):
+        filename = get_resource_path('import.csv')
+        new_file, tmp = tempfile.mkstemp()
+        copyfile(filename, tmp)
+
+        setattr(settings, "DAILY_IMPORT_FILE", tmp)
+        CronJobLog.objects.all().delete()
+
+        job = ImporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("import is successful", status)
+        self.assertEqual(1, len(mail.outbox))