"""Test the views of the ``trainings`` app"""

import json
from datetime import timedelta
from mock import call, patch

from django.contrib import messages
from django.core.urlresolvers import reverse
from django.template.loader import render_to_string
from django.test import override_settings
from django.utils import timezone
from django.utils.http import urlquote
from django.utils.translation import ugettext_lazy as _

from mtp.apps.accounts.factories import (
    BusinessUnitFactory,
    GroupRoleFactory,
    JobProfileFactory,
    TrainerFactory)
from mtp.apps.common.tests.test_cases import DEFAULT_PASSWORD, MTPTestCase, MTPBaseViewsTestCase
from mtp.apps.common.tests.waffle_test_utils import override_switch
from mtp.apps.metaplans.models import MetaplanTemplate
from mtp.apps.surveys.factories import PoolFactory, QuestionFactory
from mtp.apps.surveys.models import Question
from mtp.apps.tasks.models import TASK_STATES

from ..constants import ACTIVITY_LAUNCH_ACTION, MODULE_CHOICES
from ..factories import TrainingFactory, TrainingTaskFactory
from ..models import Training, TrainingSession, TASK_TYPES, TrainingTask
from ..views import (
    TrainingListView, TrainingSessionCreate,
    TrainingCreateDraftView
)

from .test_cases import TrainingWithActivitiesTestCase


class BaseTrainingViewsTestCase(MTPBaseViewsTestCase):
    """Base test case to use when testing the training views"""

    @classmethod
    def setUpTestData(cls):
        """Some data needed for the test"""

        super().setUpTestData()

        # Associate the new_trainer with one job_profile
        cls.job_profile = JobProfileFactory()
        GroupRoleFactory(user=cls.new_trainer, group=cls.job_profile)

        # Associate the new_trainer with one business_unit
        business_unit = BusinessUnitFactory()
        GroupRoleFactory(user=cls.new_trainer, group=business_unit)


class TrainingListViewTestCase(BaseTrainingViewsTestCase, TrainingWithActivitiesTestCase):
    """The tests for the list view of the ``Training`` model"""

    def test_training_list_view_as_trainer(self):
        """Test the training list view methods with the "trainer" user"""

        view = self.get_view(view=TrainingListView(), user=self.trainer)

        # This user own one training
        self.assertEqual(len(view.get_queryset()), 1)

    def test_training_list_view_as_new_trainer(self):
        """Test the training list view methods with the "new_trainer" user"""

        view = self.get_view(view=TrainingListView(), user=self.new_trainer)

        # This user don't own any training
        self.assertEqual(len(view.get_queryset()), 0)

    def test_training_list_view_with_business_unit_filter(self):
        """Test the training list view methods with business unit filters"""

        self.login_as_trainer()

        business_unit = BusinessUnitFactory()
        GroupRoleFactory(
            group=business_unit,
            user=self.trainer
        )
        training_list_url = '%s?business_unit=%s' % (
            Training.get_list_url(),
            business_unit.pk
        )

        response = self.client.get(training_list_url)

        # No training associated with this brand new business unit
        self.assertEqual(
            len(response.context['training_list']),
            0
        )

        # Create one using this business unit
        TrainingFactory(owner=self.trainer, business_unit=business_unit)

        response = self.client.get(training_list_url)

        # We got the new training
        self.assertEqual(
            len(response.context['training_list']),
            1
        )

    @override_switch(name='enable_assessments', active=True)
    def test_training_list_view_with_job_profile_filter(self):
        """Test the training list view methods with job profile filters"""

        self.login_as_trainer()

        job_profile = JobProfileFactory()
        GroupRoleFactory(
            group=job_profile,
            user=self.trainer
        )
        training_list_url = '%s?job_profile=%s' % (
            Training.get_list_url(),
            job_profile.pk
        )

        response = self.client.get(training_list_url)

        # No training associated with this brand new job profile
        self.assertEqual(
            len(response.context['training_list']),
            0
        )

        # Create one using this job profile
        TrainingFactory(owner=self.trainer, job_profile=job_profile)

        response = self.client.get(training_list_url)

        # We got the new training
        self.assertEqual(
            len(response.context['training_list']),
            1
        )

    def test_training_list_view_includes_waiting_tasks(self):
        """Test that if the user launched tasks about trainings, they are displayed"""

        self.login_as_admin()
        # Create other trainings like the first one
        admin_training2, admin_training3, admin_training4 = TrainingFactory.create_batch(
            3,
            owner=self.admin,
            business_unit=self.business_unit,
            job_profile=self.job_profile,
            theme=self.theme,
        )

        # Create a business unit for filtering (without trainings, just to have nothing in the list)
        business_unit = BusinessUnitFactory()
        GroupRoleFactory(group=business_unit, user=self.admin)

        # At first, no waiting trainings
        response = self.client.get(Training.get_list_url())
        self.assertNotIn('waiting_tasks_grouped', response.context)
        self.assertNotIn('waiting-trainings', str(response.content))

        # Even if no training at all in the list (ensure we don't have a 500 even if no trainings)
        response = self.client.get(
            '%s?business_unit=%s' % (Training.get_list_url(), business_unit.pk)
        )
        self.assertEqual(len(response.context['training_list']), 0)
        self.assertNotIn('waiting_tasks_grouped', response.context)
        self.assertNotIn('waiting-trainings', str(response.content))

        # Create some fake tasks, we set the started date to know the order
        now = timezone.now()

        # Two duplicate for the second training
        TrainingTaskFactory.create_batch(2, training=admin_training2,
                                         user=self.admin,
                                         task_type=TASK_TYPES.DUPLICATE_TO_MEMBER,
                                         started_at=now - timedelta(minutes=10))
        # One draft for the third training
        TrainingTaskFactory(training=admin_training3, user=self.admin,
                            task_type=TASK_TYPES.CREATE_DRAFT,
                            started_at=now - timedelta(minutes=8))
        # One duplicate for the fourth training
        TrainingTaskFactory(training=admin_training4, user=self.admin,
                            task_type=TASK_TYPES.DUPLICATE_TO_MEMBER,
                            started_at=now - timedelta(minutes=6))

        # One FINISHED duplicate for the fourth training
        TrainingTaskFactory(training=admin_training4, user=self.admin,
                            task_type=TASK_TYPES.DUPLICATE_TO_MEMBER,
                            state=TASK_STATES.SUCCESS,
                            started_at=now - timedelta(minutes=4))

        # And a task for another user
        TrainingTaskFactory(training=self.training, user=self.trainer,
                            task_type=TASK_TYPES.CREATE_DRAFT,
                            started_at=now - timedelta(minutes=2))

        # Now, display the list again, we should have our waiting trainings
        response = self.client.get(Training.get_list_url())

        # Ensure the context is ok
        self.assertIn('waiting_tasks_grouped', response.context)
        self.assertDictEqual(
            response.context['waiting_tasks_grouped'],
            TrainingTask.objects.group_tasks(TrainingTask.objects.filter(
                user=self.admin,
                state__in=TASK_STATES.UNREADY_STATES.values,
                training_id__in=list(
                    response.context['training_list'].values_list('pk', flat=True)
                ),
            ))
        )
        self.assertIn('waiting_tasks_grouped_json', response.context)
        self.assertDictEqual(
            json.loads(response.context['waiting_tasks_grouped_json']),
            TrainingTask.objects.group_tasks_for_json(TrainingTask.objects.filter(
                user=self.admin,
                state__in=TASK_STATES.UNREADY_STATES.values,
                training_id__in=list(
                    response.context['training_list'].values_list('pk', flat=True)
                ),
            ))
        )

        # Do some checks in the html
        html = str(response.content)
        self.assertEqual(html.count('<thead class="waiting-trainings">'), 1)
        self.assertEqual(html.count('<tbody class="waiting-trainings">'), 1)
        self.assertEqual(html.count('<tr class="waiting-training"'), 3)
        self.assertEqual(html.count('0 / 2'), 1)
        self.assertEqual(html.count('0 / 1'), 2)
        self.assertIn('WaitingTrainingsPoller.init', html)

        # Now, listing on a job profile without these trainings should display no waiting ones
        response = self.client.get(
            '%s?business_unit=%s' % (Training.get_list_url(), business_unit.pk)
        )
        self.assertNotIn('waiting_tasks_grouped', response.context)
        self.assertNotIn('waiting-trainings', str(response.content))

        # The other user should see its own waiting tasks
        self.client.logout()
        self.login_as_trainer()
        response = self.client.get(Training.get_list_url())

        # Ensure the context is ok
        self.assertIn('waiting_tasks_grouped', response.context)
        self.assertDictEqual(
            response.context['waiting_tasks_grouped'],
            TrainingTask.objects.group_tasks(TrainingTask.objects.filter(
                user=self.trainer,
                state__in=TASK_STATES.UNREADY_STATES.values,
                training_id__in=list(
                    response.context['training_list'].values_list('pk', flat=True)
                ),
            ))
        )
        self.assertIn('waiting_tasks_grouped_json', response.context)
        self.assertDictEqual(
            json.loads(response.context['waiting_tasks_grouped_json']),
            TrainingTask.objects.group_tasks_for_json(TrainingTask.objects.filter(
                user=self.trainer,
                state__in=TASK_STATES.UNREADY_STATES.values,
                training_id__in=list(
                    response.context['training_list'].values_list('pk', flat=True)
                ),
            ))
        )

        # Do some checks in the html
        html = str(response.content)
        self.assertEqual(html.count('<thead class="waiting-trainings">'), 1)
        self.assertEqual(html.count('<tbody class="waiting-trainings">'), 1)
        self.assertEqual(html.count('<tr class="waiting-training"'), 1)
        self.assertEqual(html.count('0 / 1'), 1)
        self.assertIn('WaitingTrainingsPoller.init', html)

        # And another trainer should see nothing
        self.client.logout()
        trainer2 = TrainerFactory()
        trainer2.set_password(DEFAULT_PASSWORD)
        trainer2.save()
        self.login_as_trainer(trainer2.username)

        response = self.client.get(Training.get_list_url())
        html = str(response.content)
        self.assertNotIn('waiting_tasks_grouped', response.context)
        self.assertNotIn('waiting_tasks_grouped_json', response.context)
        self.assertNotIn('waiting-trainings', html)
        self.assertNotIn('WaitingTrainingsPoller.init', html)

    def test_training_list_view_includes_recent_trainings(self):
        """Test that if some recent trainings exist for the user, they are highlighted"""

        # The trainer have a recent training
        self.login_as_trainer()

        # Do some checks in the html
        response = self.client.get(Training.get_list_url())
        html = str(response.content)

        # The list of recent trainings should be there
        self.assertEqual(html.count('<tbody class="recent-trainings">'), 1)

        # And our training should appear only once: in the recent training list, with a notice
        self.assertEqual(html.count('<tr class="training_item_%s' % self.training.pk), 1)
        self.assertEqual(html.count('<aside>%s</aside>' % _('Recently created')), 1)

        # But another trainee won't see it
        self.client.logout()
        trainer2 = TrainerFactory()
        trainer2.set_password(DEFAULT_PASSWORD)
        trainer2.save()
        self.login_as_trainer(trainer2.username)

        # Do some checks in the html
        response = self.client.get(Training.get_list_url())
        html = str(response.content)

        # The list of recent trainings should not be there
        self.assertNotIn('<tbody class="recent-trainings">', html)


class WaitingAndRecentTrainingsJsonViewTestCase(TrainingWithActivitiesTestCase):
    """Test that the view returning waiting/recent trainings via ajax works

    Notes
    -----
    Data in tests are created like in ``TrainingListViewTestCase``:
    ``test_json_view_includes_waiting_tasks``
        => ``test_training_list_view_includes_waiting_tasks``
    ``test_json_view_includes_recent_trainings``
        => ``test_training_list_view_includes_recent_trainings``

    """

    url = Training.get_waiting_and_recent_json_list_url()

    @staticmethod
    def get_json_grouped_task(training, remaining):
        """Helper to get the data to compare the json of a grouped-tasks entry

        Parameters
        -----------
        training: Training
            The training for which we want the grouped-tasks entry
        remaining: dict
            A dict for the remaining tasks for this training with
            - as key the task type
            - as value the list of tasks

        Returns
        -------
        dict
            The dict expected to be found in an entry of the ``grouped_tasks``
            of the json result of ``WaitingAndRecentTrainingsJsonView``

        """

        return {
            'training_name': training.name,
            'tasks': {
                task_type.constant: {
                    'remaining': [task.pk for task in tasks]
                }
                for task_type, tasks in remaining.items()
            }
        }

    @staticmethod
    def get_json_task(state, task, task_type, training):
        """Helper to get the data to compare the json of a grouped-tasks entry

        Parameters
        -----------
        state: TASK_STATES
            The state the task should be
        task: TrainingTask
            The task for which this json entry should be
        task_type: TASK_TYPES
            The type of the task
        training: Training
            The training for which this task should be

        Returns
        -------
        dict
            The dict expected to be found in an entry of the ``asks``
            of the json result of ``WaitingAndRecentTrainingsJsonView``

        """

        return {
            'state': state.constant,
            'task_id': task.celery_task_id,
            'task_type': task_type,
            'training_id': training.pk
        }

    @staticmethod
    def get_recent_trainings_html(user, trainings):
        """Helper to return the html expected in the ``recent_trainings`` entry

        Parameters
        ----------
        user: MTPUser
            The user for which we want to have the trainings
        trainings: list of Training
            The trainings expected to be found in the html

        Returns
        -------
        str
            The html expected to be found

        """

        # Enhance the trainings with annotation
        trainings = Training.objects.non_deleted().filter(
            id__in=[training.pk for training in trainings]
        ).available_for_user(
            user
        ).annotate_is_duplicable_by(
            user
        ).recently_created(
            user
        )

        return render_to_string('cms/trainings/training_list_include_trs.html', {
            'training_list': trainings
        })

    def test_json_view_includes_waiting_tasks(self):
        """Test that if the user launched tasks about trainings, they are returned"""

        self.login_as_admin()
        # Create other trainings like the first one
        admin_training2, admin_training3, admin_training4 = TrainingFactory.create_batch(
            3,
            owner=self.admin,
            business_unit=self.business_unit,
            job_profile=self.job_profile,
            theme=self.theme,
        )

        # Create a business unit for filtering (without trainings, just to have nothing in the list)
        business_unit = BusinessUnitFactory()
        GroupRoleFactory(group=business_unit, user=self.admin)

        # At first, no waiting trainings
        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertJSONEqual(response.content.decode('utf-8'), {
            'tasks': [],
            'grouped_tasks': {},
        })

        # Create some fake tasks, we set the started date to know the order
        now = timezone.now()

        # Formatting as in TrainingListViewTestCase.test_training_list_view_includes_waiting_tasks

        # Two duplicate for the second training
        t2_1, t2_2 = TrainingTaskFactory.create_batch(2, training=admin_training2,
                                                      user=self.admin,
                                                      task_type=TASK_TYPES.DUPLICATE_TO_MEMBER,
                                                      started_at=now - timedelta(minutes=10))
        # One draft for the third training
        t3_1 = TrainingTaskFactory(training=admin_training3, user=self.admin,
                                   task_type=TASK_TYPES.CREATE_DRAFT,
                                   started_at=now - timedelta(minutes=8))
        # One duplicate for the fourth training
        t4_1 = TrainingTaskFactory(training=admin_training4, user=self.admin,
                                   task_type=TASK_TYPES.DUPLICATE_TO_MEMBER,
                                   started_at=now - timedelta(minutes=6))

        # One FINISHED duplicate for the fourth training
        t4_2 = TrainingTaskFactory(training=admin_training4, user=self.admin,
                                   task_type=TASK_TYPES.DUPLICATE_TO_MEMBER,
                                   state=TASK_STATES.SUCCESS,
                                   started_at=now - timedelta(minutes=4))

        # And a task for another user
        t0_1 = TrainingTaskFactory(training=self.training, user=self.trainer,
                                   task_type=TASK_TYPES.CREATE_DRAFT,
                                   started_at=now - timedelta(minutes=2))

        # Now, get the json again, we should have our waiting trainings
        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertJSONEqual(response.content.decode('utf-8'), {
            'grouped_tasks': {
                admin_training2.pk: self.get_json_grouped_task(
                    admin_training2, {TASK_TYPES.DUPLICATE_TO_MEMBER: [t2_1, t2_2]}
                ),
                admin_training3.pk: self.get_json_grouped_task(
                    admin_training3, {TASK_TYPES.CREATE_DRAFT: [t3_1]}
                ),
                admin_training4.pk: self.get_json_grouped_task(
                    admin_training4, {TASK_TYPES.DUPLICATE_TO_MEMBER: [t4_1, t4_2]}
                ),
            },
            'tasks': [
                self.get_json_task(
                    TASK_STATES.PENDING, t2_1, TASK_TYPES.DUPLICATE_TO_MEMBER, admin_training2),
                self.get_json_task(
                    TASK_STATES.PENDING, t2_2, TASK_TYPES.DUPLICATE_TO_MEMBER, admin_training2),
                self.get_json_task(
                    TASK_STATES.PENDING, t3_1, TASK_TYPES.CREATE_DRAFT, admin_training3),
                self.get_json_task(
                    TASK_STATES.PENDING, t4_1, TASK_TYPES.DUPLICATE_TO_MEMBER, admin_training4),
                self.get_json_task(
                    TASK_STATES.SUCCESS, t4_2, TASK_TYPES.DUPLICATE_TO_MEMBER, admin_training4),
            ]
        })

        # The other user should see its own waiting tasks
        self.client.logout()
        self.login_as_trainer()
        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertJSONEqual(response.content.decode('utf-8'), {
            'grouped_tasks': {
                self.training.pk: self.get_json_grouped_task(
                    self.training, {TASK_TYPES.CREATE_DRAFT: [t0_1]}
                ),
            },
            'tasks': [
                self.get_json_task(
                    TASK_STATES.PENDING, t0_1, TASK_TYPES.CREATE_DRAFT, self.training),
            ]
        })

        # And another trainer should see nothing
        self.client.logout()
        trainer2 = TrainerFactory()
        trainer2.set_password(DEFAULT_PASSWORD)
        trainer2.save()
        self.login_as_trainer(trainer2.username)

        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertJSONEqual(response.content.decode('utf-8'), {
            'tasks': [],
            'grouped_tasks': {},
        })

    def test_json_view_includes_recent_trainings(self):
        """Test that if some recent trainings exist for the user, they are returned"""

        url = self.url + '?include_recent=1'

        # The trainer have a recent training
        self.login_as_trainer()

        response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertJSONEqual(response.content.decode('utf-8'), {
            'tasks': [],
            'grouped_tasks': {},
            'recent_trainings': self.get_recent_trainings_html(self.trainer, [self.training]),
        })

        # But another trainee won't see it
        self.client.logout()
        trainer2 = TrainerFactory()
        trainer2.set_password(DEFAULT_PASSWORD)
        trainer2.save()
        self.login_as_trainer(trainer2.username)

        response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertJSONEqual(response.content.decode('utf-8'), {
            'tasks': [],
            'grouped_tasks': {},
            'recent_trainings': self.get_recent_trainings_html(trainer2, []),
        })


class TrainingCreateViewTestCase(TrainingWithActivitiesTestCase):
    """The tests for the create view of the ``Training`` model"""

    def test_owner_should_be_saved(self):
        """Test that the "owner" of a ``Training`` should correctly set when saved"""

        # Ensure the user have no trainings in his collection
        self.assertEqual(len(self.new_trainer.trainings.all()), 0)

        self.client.login(username=self.new_trainer.username,
                          password=DEFAULT_PASSWORD)

        self.client.post(self.training.get_create_url(), {
            'name': 'training name',
            'theme': self.theme.pk
        })

        # Now, the user have one training (because the owner was correctly set)
        self.assertEqual(len(self.new_trainer.trainings.all()), 1)

    def test_external_id_should_be_saved(self):
        """Test that the "external_id" of a ``Training`` should correctly set when saved"""

        training_external_id = '2015-11-SALE-ASSESS'

        self.login_as_trainer()

        self.client.post(self.training.get_create_url(), {
            'name': 'my very unique name',
            'theme': self.theme.pk,
            '_external_id': training_external_id
        })

        new_training = Training.objects.first()
        self.assertEqual(new_training.external_id, training_external_id)


class TrainingSurveyFlagTestCase(MTPTestCase):
    """Test that the waffle switch `enable_surveys` is used in training views"""

    @classmethod
    def setUpTestData(cls):
        """Create a survey pool with a question"""

        super().setUpTestData()

        QuestionFactory(
            question_type=Question.QUESTION_TYPES.OPEN,
            value_type=Question.VALUE_TYPES.NONE,
            pool=PoolFactory(author=cls.trainer),
        )

        cls.survey_url = cls.training.get_survey_url()

    @override_switch('enable_surveys', False)
    def test_no_survey_buttons_absent_on_trainings_if_surveys_waffle_flag_off(self):
        """Test that the survey buttons are absent if switch is off"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_list_url())

        self.assertFalse(response.context['user_can_have_survey'])
        self.assertNotIn(self.survey_url, str(response.content))

    @override_switch('enable_surveys', True)
    def test_survey_buttons_present_on_trainings_if_surveys_waffle_flag_on(self):
        """Test that the survey buttons are present if switch is on"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_list_url())

        self.assertTrue(response.context['user_can_have_survey'])
        self.assertIn(self.survey_url, str(response.content))

    @override_switch('enable_surveys', False)
    def test_no_survey_button_absent_on_training_if_surveys_waffle_flag_off(self):
        """Test that the survey button is absent if switch is off"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_edit_url())

        self.assertFalse(response.context['user_can_have_survey'])
        self.assertNotIn(self.training.get_survey_url(), str(response.content))

    @override_switch('enable_surveys', True)
    def test_survey_button_present_on_training_if_surveys_waffle_flag_on(self):
        """Test that the survey button is present if switch is on"""

        self.login_as_trainer()

        response = self.client.get(self.training.get_edit_url())

        self.assertTrue(response.context['user_can_have_survey'])
        self.assertIn(self.training.get_survey_url(), str(response.content))


class BaseTrainingEditViewTestCase:
    """Base class to use when testing the views from """

    def test_owner_should_be_accepted(self):
        """Test that the owner can correctly access the view"""

        self.login_as_trainer()

        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 200)

    def test_not_owner_should_be_rejected(self):
        """Test the no-owner cannot access the view"""

        self.login_as_admin()

        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 404)


class TrainingUpdateViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the update view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        cls.url = cls.training.get_edit_url()


class TrainingPublishViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the publish view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts can be published
        cls.training = cls.training.create_draft()
        cls.url = cls.training.get_publish_version_state_url()

    def test_publish_view_should_publish_the_training(self):
        """Test that the publish view should correctly publish the training"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.training.refresh_from_db()  # We need this

        self.assertEqual(self.training.version_state,
                         self.training.VERSION_STATES.PUBLISHED)


class TrainingCreateDraftViewTestCase(BaseTrainingEditViewTestCase, TrainingWithActivitiesTestCase):
    """The tests for the create draft view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        cls.url = cls.training.get_create_draft_url()

    def test_get_if_not_in_progress_should_allow_creating_a_draft(self):
        """Test that when a draft creation is not in progress, it's marked as allowed in the view"""

        self.login_as_trainer()
        response = self.client.get(self.url)

        self.assertTrue(response.context['create_draft_allowed'])
        self.assertNotIn('create_draft_not_allowed_reason', response.context)

        # We should have the form in the response
        self.assertIn(
            '<form action="%s"' % self.training.get_create_draft_url(),
            str(response.content)
        )

    def test_get_if_in_progress_should_deny_creating_a_draft(self):
        """Test that when a draft creation is in progress, it's marked as allowed in the view"""

        self.login_as_trainer()

        # Simulate a draft creation in progress
        self.training.redis_instance.lock_create_draft(owner=self.training.owner)

        response = self.client.get(self.url)

        self.assertFalse(response.context['create_draft_allowed'])
        self.assertTrue(len(response.context['create_draft_not_allowed_reason']))

        # We should not have the form in the response
        self.assertNotIn(
            '<form action="%s"' % self.training.get_create_draft_url(),
            str(response.content)
        )

    def test_post_if_not_in_progress_should_create_a_draft(self):
        """Test that the create draft view should correctly create a draft."""

        self.login_as_trainer()

        # We'll only check if the ``create_draft`` method is called
        with patch.object(Training, 'create_draft') as patched:
            response = self.client.post(self.url)

        # The method ``create_draft`` should have been called one times, in async mode
        patched.assert_called_once_with(
            author=self.trainer,
            async=True,
            skip_in_progress=True,
        )
        # We should have been redirected to the list of trainings
        self.assertRedirects(response, self.training.get_list_url())

        # And we should have a success message
        message_found = False
        for message in messages.get_messages(response.wsgi_request):
            if message.message == TrainingCreateDraftView.success_message:
                message_found = message.level == messages.constants.SUCCESS

        if not message_found:
            self.fail('Expected success message not found')

    def test_post_if_in_progress_should_not_create_a_draft(self):
        """Test that the create draft view should not create a draft if already in progress.

        Notes
        -----
        This notes is copied from the ``post`` method of the ``TrainingCreateDraftView`` view:

        In the ``get`` method we stop the user if a new draft is being created.
        We don't do it in this ``post`` method, by passing ``skip_in_progress=True``, because it
        really should happen very rarely, when the user submits the form twice. So we simply
        ignore and do nothing if a draft creation is already in progress, and give the user its
        success message.

        """

        self.login_as_trainer()

        # Simulate a draft creation in progress
        self.training.redis_instance.lock_create_draft(owner=self.training.owner)

        # We'll only check if the ``create_draft`` method is called
        with patch.object(Training, 'create_draft') as patched:
            response = self.client.post(self.url)

        # The method ``create_draft`` should have been called one times, in async mode
        patched.assert_called_once_with(
            author=self.trainer,
            async=True,
            skip_in_progress=True,
        )

        # We should have been redirected to the list of trainings
        self.assertRedirects(response, self.training.get_list_url())

        # And we should have a success message
        message_found = False
        for message in messages.get_messages(response.wsgi_request):
            if message.message == TrainingCreateDraftView.success_message:
                message_found = message.level == messages.constants.SUCCESS

        if not message_found:
            self.fail('Expected success message not found')


class TrainingDuplicateViewTestCase(TrainingWithActivitiesTestCase):
    """The tests for the training duplicate view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test, set the trainer as the manager of the business unit"""

        super().setUpTestData()

        cls.url = cls.admin_training.get_duplicate_url()

    def test_view_on_get_request(self):
        """Test that we have the correct number of duplicates in the context."""

        self.login_as_admin()
        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.template_name[0], 'trainings/training_duplicate.html')

        # We will have 2 duplicate:
        # - for ``self.new_trainer`` (will have a ``PUBLISHED`` one)
        # - for ``self.trainer_manager`` (will have a ``DRAFT``)
        self.assertEqual(response.context_data['total_duplicates_nb'], 2)

    def test_view_on_post_request(self):
        """Test that the ``duplicate_to_member`` method is correctly called."""

        self.login_as_admin()

        # We'll only check if the ``duplicate_to_member`` method is called
        with patch.object(Training, 'duplicate_to_member') as patched:
            response = self.client.post(self.url)

        # The method ``duplicate_to_member`` should have been called three times, in async mode
        patched.assert_has_calls(
            [
                call(
                    new_state=self.training.VERSION_STATES.PUBLISHED,
                    params={
                        'owner_id': self.new_trainer.pk,
                        'business_unit_id': self.business_unit.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                    author=self.admin,
                ),
                call(
                    new_state=self.training.VERSION_STATES.DRAFT,
                    params={
                        'owner_id': self.trainer_manager.pk,
                        'business_unit_id': self.business_unit_2.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                    author=self.admin,
                ),
            ], any_order=True
        )

        self.assertRedirects(response, Training.get_list_url())


class TrainingPublishDuplicateTestCase(TrainingWithActivitiesTestCase):
    """The tests for the training publish and duplicate view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts can be published
        cls.draft_training = cls.admin_training.create_draft()
        cls.url = cls.draft_training.get_publish_and_share_url()

    def test_view_on_get_request(self):
        """Test that we have the correct number of duplicates in the context."""

        self.login_as_admin()
        response = self.client.get(self.url)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['total_duplicates_nb'], 3)

    def test_view_should_publish_and_duplicate_the_training(self):
        """Test that the publish view should correctly publish the training"""

        self.login_as_admin()

        with patch.object(Training, 'duplicate_to_member') as patched:
            response = self.client.post(self.url)

        patched.assert_has_calls(
            [
                call(
                    new_state=self.training.VERSION_STATES.PUBLISHED,
                    params={
                        'owner_id': self.new_trainer.pk,
                        'business_unit_id': self.business_unit.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                    author=self.admin,
                ),
                call(
                    new_state=self.training.VERSION_STATES.DRAFT,
                    params={
                        'owner_id': self.trainer_manager.pk,
                        'business_unit_id': self.business_unit_2.pk,
                    },
                    async=True,
                    skip_in_progress=True,
                    author=self.admin,
                ),
            ], any_order=True
        )

        self.assertRedirects(response, Training.get_list_url())

        # Our draft training should be now in a "PUBLISHED" state
        self.draft_training.refresh_from_db()
        self.assertEqual(
            self.draft_training.version_state,
            self.draft_training.VERSION_STATES.PUBLISHED
        )


class TrainingArchiveViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the training archive view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        cls.url = cls.training.get_archive_version_state_url()

    def test_archive_view_should_archive_the_training(self):
        """"Test that the delete view should correctly archive the training"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.training.refresh_from_db()

        # The training is now archived
        self.assertEqual(
            self.training.version_state,
            Training.VERSION_STATES.ARCHIVED
        )

    def test_archive_view_with_archive_children_should_archive_the_children(self):
        """"Test that the delete view should correctly archive the training and its children"""

        self.login_as_admin()
        self.client.post(
            self.admin_training.get_archive_version_state_url(),
            {'archive_children': True}
        )

        self.admin_training.refresh_from_db()

        # The training is now archived
        self.assertEqual(
            self.admin_training.version_state,
            Training.VERSION_STATES.ARCHIVED
        )

        # And the trainer's training also
        self.assertEqual(
            self.training.version_state,
            Training.VERSION_STATES.ARCHIVED
        )


class TrainingDeleteViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the training delete view of the ``Training`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts can be deleted
        cls.training = cls.training.create_draft()
        cls.url = cls.training.get_delete_url()

    def test_delete_view_should_soft_delete_the_training(self):
        """"Test that the delete view should correctly soft-delete the training"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.training.refresh_from_db()  # We need this

        # The training is now soft-deleted
        self.assertEqual(self.training.is_deleted, True)


class TrainingOptionsEditViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the edit view of the ``TrainingClientOptions`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because only the drafts options can be edited
        cls.training = cls.training.create_draft()
        cls.url = cls.training.options.get_edit_url()


class TrainingActivityListViewTestCase(BaseTrainingEditViewTestCase, MTPTestCase):
    """The tests for the list view of the ``TrainingActivity`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()
        cls.url = cls.training.get_activity_list_url()


class TrainingActivityDeleteViewTestCase(BaseTrainingEditViewTestCase,
                                         TrainingWithActivitiesTestCase):
    """The tests for the delete view of the ``TrainingActivity`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because we can only edit / delete the activities
        # from a draft training
        cls.training = cls.training.create_draft()
        cls.activity = cls.training.activities.instance_of(MetaplanTemplate).order_by('id').first()
        cls.url = cls.activity.get_delete_url()

    def test_delete_view_should_soft_delete_the_activity(self):
        """"Test that the delete view should correctly soft-delete the activity"""

        self.login_as_trainer()
        self.client.post(self.url)

        self.activity.refresh_from_db()  # We need this

        # The training is now soft-deleted
        self.assertEqual(self.activity.is_deleted, True)


class TrainingActivityDuplicateViewTestCase(BaseTrainingEditViewTestCase,
                                            TrainingWithActivitiesTestCase):
    """The tests for the duplicate view of the ``TrainingActivity`` model"""

    @classmethod
    def setUpTestData(cls):
        """Define the url to test"""

        super().setUpTestData()

        # We must create a draft training, because we can only duplicate activities
        # from a draft training
        cls.training = cls.training.create_draft()
        cls.activity = cls.training.activities.instance_of(MetaplanTemplate).order_by('id').first()
        cls.url = cls.activity.get_duplicate_url()

    def test_duplicate_view_should_duplicate_the_activity(self):
        """"Test that the duplicate view should correctly duplicate the activity"""

        # Initial activities
        activities_count = self.training.activities.count()

        self.login_as_trainer()
        self.client.post(self.url)

        # The training got one more activity
        self.assertEqual(self.training.activities.count(),
                         activities_count + 1)


class TrainingSessionListViewTestCase(MTPTestCase):
    """The tests for the list view of the ``TrainingSession`` model"""

    def test_session_list_view_without_training_for_trainer(self):
        """Test the output of the session list methods without a training filter"""

        self.login_as_trainer()

        # Call the view with the trainer user
        response = self.client.get(reverse('trainings:session-list'))

        self.assertEqual(
            response.status_code,
            200
        )

        # The trainer see one session (for the training he owns)
        self.assertEqual(
            len(response.context['trainingsession_list']),
            1
        )

    def test_session_list_has_next_url_param_for_edit_and_close_links(self):
        """Test the next url param is injected into the edit link"""

        self.login_as_trainer()

        # let's add a GET param to be sure it is injected too
        list_url = '%s%s' % (reverse('trainings:session-list'), '?page=1')
        response = self.client.get(list_url)

        # the next url param should be present in the edit link and link back
        # to the sessions list page
        list_url_encoded = urlquote(list_url)

        edit_link = '%s?next=%s' % (
            self.training_session.get_edit_url(),
            list_url_encoded
        )
        self.assertIn(
            edit_link,
            response.rendered_content
        )

        close_link = '%s?next=%s' % (
            self.training_session.get_close_url(),
            list_url_encoded
        )
        self.assertIn(
            close_link,
            response.rendered_content
        )

    def test_session_list_view_without_training_for_trainee(self):
        """Test the output of the session list methods without a training filter"""

        self.login_as_trainee()

        # Call the view with the trainee user
        response = self.client.get(reverse('trainings:session-list'))

        self.assertEqual(
            response.status_code,
            200
        )

        # The trainee see one session (he was added into)
        self.assertEqual(
            len(response.context['trainingsession_list']),
            1
        )

    def test_session_list_view_with_job_profile_manager(self):
        """Test that we have a ``business_unit`` filter with a job profile manager"""

        self.login_as_admin()

        # Call the view with the admin user
        response = self.client.get(reverse('trainings:session-list'))

        self.assertEqual(
            response.status_code,
            200
        )

        # The business unit field is present
        self.assertIn(
            'id_training__business_unit',
            response.content.decode('utf-8')
        )


class TrainingSessionEditViewsTestCase(MTPBaseViewsTestCase, MTPTestCase):
    """The tests for the edit views of the ``TrainingSession`` model"""

    def setUp(self):
        """Login as a trainer"""

        super().setUp()

        self.login_as_trainer()

    def test_all_routes_should_work(self):
        """Test the response_code of all the session edit routes"""

        urls_to_test = [
            self.training_session.get_edit_url(),
            self.training_session.get_delete_url(),
            self.training_session.get_whoswho_url(),
            self.training_session.get_close_url(),
            self.training_session.get_create_url(),
            self.training_session.get_podium_url(),
            self.training.get_session_create_url(),
        ]

        for url in urls_to_test:
            response = self.client.get(url)
            self.assertEqual(response.status_code, 200)

    def test_session_create_view_without_a_training_as_a_trainer(self):
        """Test the behaviour of the session create view, as a trainer"""

        view = self.get_view(view=TrainingSessionCreate(), user=self.trainer)

        self.assertEqual(
            view.get_form_kwargs().get('remove_trainer_field'),
            True
        )
        self.assertEqual(
            view.get_form_kwargs().get('current_user'),
            self.trainer
        )

    def test_session_create_view_without_a_training_as_an_admin(self):
        """Test the behaviour of the session create view, as an admin"""

        view = self.get_view(view=TrainingSessionCreate(), user=self.admin)

        self.assertEqual(
            view.get_form_kwargs().get('remove_trainer_field'),
            False
        )
        self.assertEqual(
            view.get_form_kwargs().get('current_user'),
            self.admin
        )

    def test_save_the_session_should_set_the_trainer_as_a_trainer(self):
        """Test that the create session, as a trainer, should set the trainer"""

        # One already created session
        self.assertEqual(
            TrainingSession.objects.filter(trainer=self.trainer).count(),
            1
        )

        response = self.client.post(self.training.get_session_create_url(), {
            'training': self.training.pk,
            'start_date': '2015-01-01 18:30',
            'end_date': '2015-01-01 18:30',
            'state': 'op',
            'permissions': 'pu'
        }, follow=True)

        self.assertEqual(
            response.status_code,
            200  # Redirected after the save
        )

        # The trainer now got two sessions
        self.assertEqual(
            TrainingSession.objects.filter(trainer=self.trainer).count(),
            2
        )

    def test_save_the_session_should_not_set_the_trainer_as_an_admin(self):
        """Test that editing a session, as an admin, should not modify the trainer"""

        # The session is associated with the trainer
        self.assertEqual(
            self.training_session.trainer_id,
            self.trainer.id
        )

        self.login_as_admin()

        # Modify the session
        response = self.client.post(self.training_session.get_edit_url(), {
            'start_date': '2015-01-01 18:30',
            'end_date': '2015-01-01 18:30',
            'state': 'du',
            'permissions': 'op'
        }, follow=True)

        self.assertEqual(
            response.status_code,
            200  # Redirected after the save
        )

        self.training_session.refresh_from_db()

        # The session is still associated with the trainer
        self.assertEqual(
            self.training_session.trainer_id,
            self.trainer.id
        )

    def test_save_session_view_should_redirect_to_next_url(self):
        """Test that the redirect url with ?next mecanism works"""

        next_url = '/cms/trainings/sessions/?page=2'

        response = self.client.post(
            '%s?next=%s' % (self.training_session.get_edit_url(), next_url),
            {
                'trainer': self.training_session.trainer.pk,
                'start_date': '2015-01-01 18:30',
                'end_date': '2015-01-01 18:30',
                'state': 'op',
                'permissions': 'pu'
            },
            follow=True
        )

        self.assertIn(
            ('http://testserver%s' % next_url, 302),
            response.redirect_chain
        )

    def test_close_session_view_should_close_the_session(self):
        """Test that the close session view should correctly close the session"""

        self.assertEqual(self.training_session.state,
                         self.training_session.STATE_CHOICES.OPEN)

        self.client.post(self.training_session.get_close_url())

        self.training_session.refresh_from_db()  # Necessary

        self.assertEqual(self.training_session.state,
                         self.training_session.STATE_CHOICES.CLOSED)

    def test_close_session_view_should_redirect_to_next_url(self):
        """Test that the redirect url with ?next mecanism works"""

        next_url = '/cms/trainings/sessions/?page=2'

        response = self.client.post(
            '%s?next=%s' % (self.training_session.get_close_url(), next_url),
            follow=True
        )

        self.assertIn(
            ('http://testserver%s' % next_url, 302),
            response.redirect_chain
        )


class ActivityQRCodeDownloadViewTestCase(TrainingWithActivitiesTestCase):
    """Test case that tests the launch QRCode download."""

    def test_get_qrcode_data(self):
        """Test get qrcode data which be stored in QRCode."""

        activity = self.workshop
        qrcode_data = activity.get_qrcode_data()

        self.assertEqual(qrcode_data['id'], activity.id)
        self.assertEqual(qrcode_data['type'], MODULE_CHOICES.WORKSHOP)
        self.assertEqual(qrcode_data['action'], ACTIVITY_LAUNCH_ACTION)
        self.assertEqual(qrcode_data['original_version'], activity.id)

        duplicated_activity = activity.duplicate()
        qrcode_data = duplicated_activity.get_qrcode_data()

        self.assertEqual(qrcode_data['id'], duplicated_activity.id)
        self.assertEqual(qrcode_data['type'], MODULE_CHOICES.WORKSHOP)
        self.assertEqual(qrcode_data['action'], ACTIVITY_LAUNCH_ACTION)
        self.assertEqual(qrcode_data['original_version'], duplicated_activity.original_version_id)

    def test_qrcode_download_detail_view_work(self):
        """Test that the download detail view of QR Code work."""

        activity = self.workshop
        url = activity.get_qrcode_url()

        self.login_as_trainer()
        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)

    def test_qrcode_download_list_view_work(self):
        """Test that the download list view of QR Code work."""

        url = self.training.get_qrcodes_url()

        self.login_as_trainer()
        response = self.client.get(url)

        self.assertEqual(response.status_code, 200)


class TrainingSessionExportPhotostoryTestCase(MTPTestCase):
    """Test case for the ``TrainingSessionExportPhotostory`` view"""

    def test_photostory_export_view_should_return_200_if_photos_were_taken(self):
        """Test that the view should return a 200 if photos were taken"""

        self.login_as_trainer()

        with patch.object(TrainingSession, 'can_download_photostory_images', return_value=True):

            response = self.client.get(self.training_session.get_photostory_export_url())

            self.assertEqual(response.status_code, 200)

    def test_photostory_export_view_should_return_404_if_no_photos_were_taken(self):
        """Test that the view should return a 404 error if no photos were taken"""

        self.login_as_trainer()

        with patch.object(TrainingSession, 'can_download_photostory_images', return_value=False):

            response = self.client.get(self.training_session.get_photostory_export_url())

            self.assertEqual(response.status_code, 404)
