"""This module holds the views for the ``trainings`` app."""

import codecs
import io
import json
import os
import zipfile

from subprocess import call
from time import sleep

from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.formats import get_format
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views import generic

from django_filters.views import FilterView
from waffle import switch_is_active

from mtp.apps.accounts.models import Group
from mtp.apps.common.views_utils import (
    ExportFileMixin,
    LoginRequiredMixin,
    NextSuccessUrlMixin,
    OrderableListMixin,
    RestrictToSuperuserOrTrainerMixin,
    SuccessMessageMixin,
    SyncableDeleteView,
)
from mtp.apps.documents.models import Document, Note
from mtp.apps.evaluations.models import Evaluation
from mtp.apps.eventlog.helpers import event_log
from mtp.apps.eventlog.models import ACTION_CHOICES as EVENT_ACTIONS
from mtp.apps.metaplans.models import Metaplan
from mtp.apps.qrcodes.views import (
    ObjectQRCodeDownloadDetailView,
    ObjectQRCodeDownloadListView,
    QRCodeDownloadMixin,
)
from mtp.apps.roleplays.models import Roleplay
from mtp.apps.surveys.models import Pool
from mtp.apps.tasks.views import TaskListJsonView
from mtp.apps.trainings.forms import TrainingOptionsForm
from mtp.apps.trainings.qrcodes import ActivityQRCode
from mtp.apps.workshops.models import Workshop

from .filters import TrainingFilterSet, TrainingSessionFilter
from .forms import (
    TrainingArchiveForm,
    TrainingForm,
    TrainingSessionForm,
    TrainingCreateForm,
)
from .models import (
    Training,
    TrainingActivity,
    TrainingClientOptions,
    TrainingSession,
    TrainingTask,
)


class TrainingBaseView(RestrictToSuperuserOrTrainerMixin):
    """Base view for all views related to the trainings.

    Define the queryset to use, depending on the user, and set the
    ``current_main_section``in the context.

    """

    def get_context_data(self, **kwargs):
        """Add ``current_main_section`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        if switch_is_active('enable_surveys'):
            user_can_have_survey = bool(Pool.non_deleted.count_valid_for_user(self.request.user))
        else:
            user_can_have_survey = False

        return super().get_context_data(
            current_main_section='trainings',
            user_can_have_survey=user_can_have_survey,
            **kwargs
        )

    def get_training_queryset_for_current_user(self):
        """Get all ``Training`` instances the current user is allowed to see.

        The Queryset is filtered accordingly to the training's ``owner``.

        Returns
        -------
        Queryset of ``mtp.apps.trainings.models.Training``
            A lazy queryset with the wanted trainings.

        See Also
        --------
        ``mtp.apps.trainings.managers.TrainingQueryset``

        """

        return Training.objects.non_deleted().owned_by(
            self.request.user
        ).annotate_is_duplicable_by(self.request.user)


class TrainingEditBase(SuccessMessageMixin, TrainingBaseView):
    """Base view to edit a given instance of ``mtp.apps.trainings.models.Training`` instance."""

    model = Training
    form_class = TrainingForm
    pk_url_kwarg = 'training_uuid'

    def get_form_kwargs(self):
        """Return the form kwargs accordingly to the context.

        See Also
        --------
        ``mtp.apps.trainings.forms.TrainingForm.__init__`` method.

        """
        kwargs = super().get_form_kwargs()
        kwargs['current_user'] = self.request.user

        return kwargs

    def get_context_data(self, **kwargs):
        """Add ``current_sub_section`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            current_sub_section='general_informations',
            **kwargs
        )

    def get_success_url(self):
        """Get the url where to go back when an action is done on a training.

        It actually returns the url to list of the trainings.

        """

        return self.model.get_list_url()

    def get_queryset(self):
        """The user may edit only the trainings he own."""

        return super().get_training_queryset_for_current_user().owned_by(
            self.request.user
        )


class TrainingCreateView(TrainingEditBase, generic.CreateView):
    """The view to create a ``mtp.apps.training.models.Training`` instance.

    Notes
    -----
    The ``owner`` is automatically set when saving the instance.

    """

    form_class = TrainingCreateForm
    success_message = _("Your training was successfully created.")

    def form_valid(self, form):
        """Set the ``owner``, save the training and redirect the user.

        Parameters
        ----------
        form : instance of ``django.forms.models.ModelForm``
            The validated form to save.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the list url of the training.

        Notes
        -----
        This method doesn't call ``super`` but implements all the necessary
        stuff (saving and redirecting)

        """

        training = form.save(commit=False)
        training.owner = self.request.user
        training.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(training.get_edit_url())


class TrainingUpdateView(TrainingEditBase, generic.UpdateView):
    """The view to update a ``mtp.apps.training.models.Training`` instance."""

    success_message = _("Your training was successfully updated.")

    def form_valid(self, form):
        """Change the redirection to edition url if the user clicks on SaveAndContinue button.

        For the parameters, see ``django.forms.generic.edit.FormMixin``
        """

        response_redirect = super().form_valid(form)

        if 'save_and_continue' in self.request.POST:
            response_redirect = HttpResponseRedirect(self.object.get_edit_url())

        return response_redirect


class TrainingPublishView(TrainingEditBase, generic.UpdateView):
    """The view to publish a ``mtp.apps.training.models.Training`` instance.

    """

    template_name_suffix = '_publish'
    success_message = _("Your training was successfully published.")

    def get_queryset(self):
        """Filter the trainings that can be published."""

        return super().get_queryset().filter_is_publishable()

    def post(self, request, *args, **kwargs):
        """Publish the training, and redirect to the list of training.

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the created draft instance.

        """

        training = self.get_object()

        # Publish the training
        training.publish()

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingPublishDuplicateView(TrainingEditBase, generic.UpdateView):
    """The view to publish and duplicate a given ``mtp.apps.training.models.Training`` instance."""

    template_name_suffix = '_duplicate_and_share'
    success_message = _("Your training is currently being published and duplicated.")

    def get_queryset(self):
        """Filter the trainings that can be published and duplicated."""

        return super().get_queryset().filter_is_publishable().filter_is_duplicable_by(
            self.request.user
        )

    def get_context_data(self, **kwargs):
        """Get the potential duplicates of the current training.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        to_duplicate = self.get_object().get_members_to_duplicate(replace_published=False)

        return super().get_context_data(
            total_duplicates_nb=len(to_duplicate),
            **kwargs
        )

    def post(self, request, *args, **kwargs):
        """Publish the training and duplicate it.

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        instance of `django.http.HttpResponseRedirect`
            A redirect to the list of trainings.
        """

        training = self.get_object()
        training.publish()
        training.duplicate_to_members(
            replace_published=False,
            async=True,
            author=self.request.user
        )
        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingArchiveView(TrainingEditBase, generic.UpdateView):
    """View to archive a ``Training`` instance, with confirmation"""

    template_name_suffix = '_archive'
    success_message = _("Your training was successfully archived.")
    form_class = TrainingArchiveForm

    def get_queryset(self):
        """Filter the trainings that can be archived."""

        return super().get_training_queryset_for_current_user().exclude_is_archived()

    def form_valid(self, form):
        """Archive the training and the children if asked to do so.

        For the parameters, see ``django.views.generic.UpdateView``

        """

        self.object.archive(cascade=form.cleaned_data.get('archive_children', False))

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingCreateDraftView(TrainingEditBase, generic.UpdateView):
    """The view to create a new draft from a ``mtp.apps.training.models.Training``instance."""

    template_name_suffix = '_draft'
    success_message = _("The new version is currently being created.")

    def get_queryset(self):
        """Filter the trainings that can be drafted."""

        return super().get_queryset().filter_is_draftable()

    def get_context_data(self, **kwargs):
        """Check if the draft creation and add in the context the check and reason if not.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        context = super().get_context_data(**kwargs)

        training = self.object

        allowed, reason = training.is_new_draft_allowed()

        context['create_draft_allowed'] = allowed

        if not allowed:
            context['create_draft_not_allowed_reason'] = str(reason)

        return context

    def post(self, request, *args, **kwargs):
        """Create a draft from a ``mtp.apps.training.models.Training`` instance.

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the created draft instance.

        Notes
        -----
        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.get_object().create_draft(
            author=self.request.user,
            async=True,
            skip_in_progress=True,
        )

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingDuplicateView(TrainingEditBase, generic.UpdateView):
    """The view to duplicate a given ``mtp.apps.training.models.Training`` instance."""

    template_name_suffix = '_duplicate'
    success_message = _("Your training is currently being duplicated.")

    def get_queryset(self):
        """Filter the trainings that can be duplicated."""

        return super().get_queryset().filter_is_duplicable_by(self.request.user)

    def get_context_data(self, **kwargs):
        """Get the potential duplicates of the current training.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        to_duplicate = self.get_object().get_members_to_duplicate(replace_published=False)

        return super().get_context_data(
            total_duplicates_nb=len(to_duplicate),
            **kwargs
        )

    def post(self, request, *args, **kwargs):
        """Duplicate the training, and redirect the user.

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of trainings.

        """

        self.get_object().duplicate_to_members(replace_published=False, async=True,
                                               author=self.request.user)

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingDeleteView(TrainingEditBase, SyncableDeleteView):
    """View to soft-delete a ``Training`` instance, with confirmation.

    A training can only be soft-deleted if he is in the ``DRAFT`` version_state
    (so we are sure it's not used by any ``TrainingSession`` instances).

    """

    success_message = _("Your training was successfully deleted.")

    def get_queryset(self):
        """Filter the trainings that can be soft-deleted."""
        return super().get_queryset().filter_is_draft()


class TrainingListView(TrainingBaseView, FilterView):
    """View to list the ``Training`` instances the current user is allowed to see."""

    model = Training  # to be able to access ``view.model`` in template
    paginate_by = 15
    template_name = 'cms/trainings/training_list.html'
    filterset_class = TrainingFilterSet

    def get_filterset_kwargs(self, filterset_class):
        """Set the additional kwargs for the filter form

        Parameters
        ----------
        filterset_class: object
            The filter form class to use

        Returns
        -------
        dict: A dict with the updated kwargs

        """

        kwargs = super().get_filterset_kwargs(filterset_class)

        kwargs.update({
            'business_unit_queryset': self.request.user.mgroups.non_deleted().business_units(),
            'job_profile_queryset': self.request.user.mgroups.non_deleted().job_profiles()
        })

        return kwargs

    def get_queryset(self):
        """Filter the training list according to the current user"""

        return super().get_training_queryset_for_current_user().select_related('business_unit')

    def get_context_data(self, **kwargs):
        """Add information about unfinished tasks asked by the user

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """
        context = super().get_context_data(**kwargs)

        # We'll display waiting tasks and finished ones only on the first page
        display_waiting = not context.get('page_obj') or context['page_obj'].number == 1

        # And we'll do this only for trainings displayed in the current list
        if display_waiting and context['training_list']:

            # But for the whole list, ie the trainings on all pages
            if context.get('paginator'):
                all_trainings = context['paginator'].object_list
            else:
                all_trainings = context['training_list']

            training_ids = list(all_trainings.values_list('pk', flat=True))

            # Get all the tasks pending for the user having as original training a training being
            # in the  current displayed list
            tasks = TrainingTask.objects.filter(
                user=self.request.user,
                task_type__in=TrainingTask.TASK_TYPES.TRAINING_CREATION.values,
                state__in=TrainingTask.TASK_STATES.UNREADY_STATES.values,
                training_id__in=training_ids,
            ).select_related('training').order_by('started_at')

            if tasks:
                # group them by type + original training
                context['waiting_tasks_grouped'] = TrainingTask.objects.group_tasks(tasks)
                # idem but for the js
                context['waiting_tasks_grouped_json'] = json.dumps(
                    TrainingTask.objects.group_tasks_for_json(tasks)
                )

            # Get the recently created trainings
            context['recent_trainings'] = all_trainings.recently_created(self.request.user)
            context['recent_trainings_ids'] = set(
                training.pk for training in context['recent_trainings']
            )

        return context


class WaitingAndRecentTrainingsJsonView(TaskListJsonView):
    """A simple list view for ajax only that will return a json view of training tasks"""

    model = TrainingTask

    def get_queryset(self):
        """Limit the returned task to only be the ones creating trainings"""

        return super().get_queryset().filter(
            task_type__in=TrainingTask.TASK_TYPES.TRAINING_CREATION.values
        )

    def get_recent_trainings(self):
        """Return the html of the recent trainings of the users

        Returns
        -------
        str
            The html with the list of recent trainings

        """

        trainings = Training.objects.non_deleted().available_for_user(
            self.request.user
        ).annotate_is_duplicable_by(
            self.request.user
        ).recently_created(
            self.request.user
        )

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

    def render_task_to_dict(self, task):
        """Convert a task to a dict to be converted into Json to be put in the response

        Parameters
        ----------
        task: TrainingTask
            The task to convert to a dict

        Returns
        -------
        dict
            A dict with data from the task, that will be converted into Json

        """

        result = super().render_task_to_dict(task)

        result.update({
            'training_id': task.training_id,
            'task_type': task.task_type,
        })

        return result

    def render_json(self, context):
        """Prepare the json to return in ``render_to_response.

        This override the parent method to add a ``grouped_tasks`` to be used by the javascript,
        which is the result of a call to ``group_tasks`` with all the tasks in the context.

        For the parameters, see ``mtp.apps.tasks.views import TaskListJsonView``

        """

        result = super().render_json(context)
        result['grouped_tasks'] = TrainingTask.objects.group_tasks_for_json(context['object_list'])

        if self.request.GET.get('include_recent'):
            result['recent_trainings'] = self.get_recent_trainings()

        return result


class TrainingOptionsEditView(SuccessMessageMixin, TrainingBaseView, generic.UpdateView):
    """View to edit a ``TrainingClientOptions`` instance"""

    pk_url_kwarg = 'options_uuid'
    model = TrainingClientOptions
    form_class = TrainingOptionsForm
    success_message = _('The options was successfully updated.')

    @cached_property
    def training(self):
        """Try to get the current selected training from the url.

        Raises
        ------
        ``django.http.Http404``
            If the training doesn't exists.

        Returns
        -------
        ``Training`` instance

        """

        return get_object_or_404(
            super().get_training_queryset_for_current_user().filter_is_editable(),
            pk=self.kwargs['training_uuid']
        )

    def get_queryset(self):
        """Return the queryset of options the current user can edit"""

        return TrainingClientOptions.objects.filter(
            training=self.training
        )

    def get_success_url(self):
        return self.training.get_activity_list_url()


class TrainingActivityBase(TrainingBaseView):
    """Base view to manage the training activity

    Define the queryset to use, depending on the user and set the
    ``current_sub_section``in the context.

    """

    pk_url_kwarg = 'activity_uuid'
    model = TrainingActivity

    @cached_property
    def training(self):
        """Try to get the current selected training from the url.

        Raises
        ------
        ``django.http.Http404``
            If the training doesn't exists.

        Returns
        -------
        ``Training`` instance

        """

        return get_object_or_404(
            super().get_training_queryset_for_current_user(),
            pk=self.kwargs['training_uuid']
        )

    def get_queryset(self):
        """Filter the activities for the current ``Training`` instance."""

        return TrainingActivity.objects.non_deleted().for_training(self.training).order_by(
            'sort_order'
        )

    def get_context_data(self, **kwargs):
        """Add the ``current_sub_section`` and the current ``Training`` to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            current_sub_section='activities',
            training=self.training,
            **kwargs
        )

    def get_success_url(self):
        """Get the url where to go back when an action is done on a activity.

        It actually returns the url of the activity list.

        """

        return self.training.get_activity_list_url()


class TrainingActivityListView(OrderableListMixin, TrainingActivityBase, generic.ListView):
    """View to list and sort the ``Activity`` instances"""

    template_name = 'cms/trainings/trainingactivity_list.html'


class TrainingActivityEditBase(TrainingActivityBase, SuccessMessageMixin):
    """Base view for the edition of the ``Activity`` instances."""

    def get_queryset(self):
        """Filter the activity that can be edited.

        The activity can be edited only if they belong to a ``DRAFT`` training.

        Returns
        -------
        instance of mtp.apps.trainings.managers.TrainingActivityQuerySet
            The list of activities which can be edited

        """
        return super().get_queryset().editable()


class TrainingActivityDeleteView(TrainingActivityEditBase, SyncableDeleteView):
    """View to soft delete a ``TrainingActivity."""

    template_name = 'cms/trainings/trainingactivity_delete.html'
    success_message = _('Your activity was successfully deleted.')


class TrainingActivityDuplicateView(TrainingActivityEditBase, generic.UpdateView):
    """View to create a duplicate of a given ``Activity`` instance, with confirmation."""

    fields = []
    template_name = 'cms/trainings/trainingactivity_duplicate.html'
    success_message = _('Your activity was successfully duplicated.')

    def post(self, request, *args, **kwargs):
        """Duplicate the activity, and redirect the user.

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of activities.

        """

        activity = self.get_object()
        activity.duplicate()

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingSessionBaseView(LoginRequiredMixin):
    """Base view for all views related to the sessions.

    Define the queryset to use, depending on the user and set the
    ``current_main_section``in the context.

    """

    model = TrainingSession
    pk_url_kwarg = 'session_uuid'

    def get_context_data(self, **kwargs):
        """Add the ``current_main_section`` and add another queryset for trainings.

        The ``Training`` QuerySet is there to allow filtering the session.

        For the parameters, see ``django.forms.generic.base.ContextMixin``


        """

        return super().get_context_data(
            current_main_section='sessions',
            **kwargs
        )

    def get_training_queryset_for_current_user(self):
        """Get all ``Training`` instances to filter the ``TrainingSession`` list.

        The Queryset is filtered accordingly to the user's type or user's group:

        - superusers and trainers:
            Can see all the non-deleted, ``PUBLISHED`` trainings they own.
        - trainees:
            Can see only the training in which there is a session they were added to.

        Returns
        -------
        Queryset of ``mtp.apps.trainings.models.Training``
            A lazy queryset with the wanted trainings.

        Raises
        ------
        ``django.http.Http404``
            if the user is neither a superuser, a trainer, or a trainee

        """

        training_qs = Training.objects.non_deleted()

        if self.request.user.is_superuser or self.request.user.is_trainer:
            return training_qs.available_for_user(self.request.user).exclude_is_draft()

        if self.request.user.is_trainee:
            return training_qs.for_trainee(self.request.user).exclude_is_draft()

        raise Http404

    def get_queryset(self):
        """Get all ``TrainingSession`` instances the current user is allowed to see.

        The Queryset is filtered accordingly to the user's type or user's group,
        see ``visible_by_user`` method on ``mtp.apps.trainings.models.TrainingSession``.

        Returns
        -------
        Queryset of ``mtp.apps.trainings.models.TrainingSession``
            A lazy queryset with the wanted training sessions.

        """

        return TrainingSession.objects.non_deleted().visible_by_user(
            self.request.user
        ).select_related('trainer', 'training').order_by('-start_date')


class TrainingSessionList(TrainingSessionBaseView, FilterView):
    """View to list the ``TrainingSession`` instances the current user is allowed to see."""

    template_name = 'cms/trainings/trainingsession_list.html'
    filterset_class = TrainingSessionFilter
    paginate_by = 10

    @property
    def selected_training(self):
        """Get the selected training in filter.

        Returns
        -------
        Training
            The selected training or None.

        """

        # We fetch selected trainings pks in filter
        try:
            selected_trainings_pks = self.filterset.data.getlist('training')
        except AttributeError:  # No training is selected
            return None

        # If only one training is selected, we return it else None
        if len(selected_trainings_pks) == 1:
            return Training.objects.get(pk=selected_trainings_pks[0])

        return None

    def get_context_data(self, **kwargs):
        """Add the ``DATE_INPUT_FORMAT`` and the selected training to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            DATE_INPUT_FORMAT=get_format('DATE_INPUT_FORMATS')[2].replace('%', '').replace(
                'M',
                'i'
            ),
            selected_training=self.selected_training,
            **kwargs
        )

    def get_filterset_kwargs(self, filterset_class):
        """Set the additional kwargs for the filter form

        Parameters
        ----------
        filterset_class: object
            The filter form class to use

        Returns
        -------
        dict: A dict with the updated kwargs

        """

        kwargs = super().get_filterset_kwargs(filterset_class)

        kwargs.update({
            'trainings_queryset': self.get_training_queryset_for_current_user().order_by(
                '-created_at'
            ),
            'user': self.request.user
        })

        return kwargs


class TrainingSessionEditBase(SuccessMessageMixin, RestrictToSuperuserOrTrainerMixin,
                              TrainingSessionBaseView):
    """Base view to manage ``TrainingSession`` instances."""

    form_class = TrainingSessionForm

    def get_form_kwargs(self):
        """Return the form kwargs accordingly to the context.

        If the training session is not new, remove the ``training`` field on the form.
        If the user is not a superuser, remove the ``trainer`` field on the form.

        See Also
        --------
        ``mtp.apps.trainings.forms.TrainingSessionForm.__init__`` method.

        """

        kwargs = super().get_form_kwargs()
        kwargs['remove_training_field'] = bool(getattr(self, 'object', None))
        kwargs['remove_trainer_field'] = not self.request.user.is_superuser
        kwargs['current_user'] = self.request.user

        return kwargs

    def get_context_data(self, **kwargs):
        """Add the ``DATETIME_INPUT_FORMAT`` constant to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        # TODO: Refactor this.
        return super().get_context_data(
            DATETIME_INPUT_FORMAT=get_format('DATETIME_INPUT_FORMATS')[2].replace(
                '%', '').replace('M', 'i'),
            **kwargs
        )

    def get_success_url(self):
        """Return the url be redirect to after the save.

        If we got a current training instance, we redirect filtered list of sessions
        of this training, if not, simply redirect to the unfiltered list.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the url of the list of training sessions.

        """

        return TrainingSession.get_list_url()

    def form_valid(self, form):
        """Set the trainer if applicable, save the training session and redirect the user.

        Parameters
        ----------
        form : instance of ``django.forms.models.ModelForm``
            The validated form to save.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the list of training session.

        Notes
        -----
        This method doesn't call ``super`` but implements all the necessary stuff (saving and
        redirecting)

        """

        if not self.request.user.is_superuser:
            # it's a mixin, `object` will be defined in get/post of standard imported view class
            self.object = form.save(commit=False)  # pylint: disable=attribute-defined-outside-init
            self.object.trainer = self.request.user
            self.object.save()
            form.save_m2m()  # Necessary to save the session users
        else:
            form.save()

        self.send_message(form.cleaned_data)

        return HttpResponseRedirect(self.get_success_url())


class TrainingSessionCreate(TrainingSessionEditBase, generic.CreateView):
    """The view to create a ``TrainingSession`` instance."""

    success_message = _("Your session was successfully created.")


class TrainingSessionUpdate(NextSuccessUrlMixin, TrainingSessionEditBase, generic.UpdateView):
    """The view to update a ``TrainingSession`` instance."""

    success_message = _("Your session was successfully updated.")


class TrainingSessionDelete(TrainingSessionEditBase, SyncableDeleteView):
    """The view to soft-delete a ``TrainingSession`` instance."""

    success_message = _("Your session was successfully deleted.")


class TrainingSessionWhosWho(TrainingSessionBaseView, generic.DetailView):
    """The view to render a ``TrainingSession`` users list."""

    template_name = "cms/trainings/whoswho.html"

    def users(self):
        """Get the users from the ``TrainingSession`` who belongs to the "whoswho" group."""

        group = Group.objects.get(name='whoswho')
        return self.object.users.filter(mgroups=group).order_by('last_name', 'first_name')

    def get_context_data(self, **kwargs):
        """Add the list of users who belongs to the "whoshwo" group to the context.

        For the parameters, see ``django.forms.generic.base.ContextMixin``
        """

        return super().get_context_data(
            users=self.users,
            **kwargs
        )


class TrainingSessionClose(NextSuccessUrlMixin, TrainingSessionEditBase, generic.UpdateView):
    """The view close a ``mtp.apps.training.models.TrainingSession`` instance."""

    template_name_suffix = '_close'
    success_message = _("Your training session was successfully closed.")

    def post(self, request, *args, **kwargs):
        """Close the training session, and redirect to the list of training session.

        For the parameters, see ``django.views.generic.View``

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the training session list.

        """

        training_session = self.get_object()
        training_session.close(async=True)

        self.send_message()

        return HttpResponseRedirect(self.get_success_url())


class TrainingSessionPodium(TrainingSessionEditBase, generic.DetailView):
    """The view to render a "podium" of the session's trainees"""

    template_name_suffix = '_podium'


class TrainingSessionExportPhotostory(ExportFileMixin, TrainingSessionEditBase, generic.DetailView):
    """The view to export the photostory images for a given session"""

    response_class = HttpResponse
    content_type = 'application/x-zip-compressed'

    def get_filename(self):
        """Build the name of the exported photostory zip file

        Returns
        -------
        str
            The filename of the photostory zip file

        """

        return '%s-%s' % (
            self.get_object().training.name,
            self.get_object().start_date.date()
        )

    def render_to_response(self, *args, **response_kwargs):  # pylint: disable=unused-argument
        """Build the zip containing the photostory images and return it

        For the parameters, see ``django.views.generic.DetailView``"""

        training_session = self.get_object()

        if not training_session.can_download_photostory_images():
            raise Http404

        zip_buffer = io.BytesIO()
        zip_file = zipfile.ZipFile(zip_buffer, 'w')

        participants = list(training_session.users.non_deleted())
        participants.append(training_session.trainer)

        image_name = '%(training_name)s_%(date)s_session_%(session_uuid)s_%(trainee)s_%(index)d.jpg'

        # For each participant, rename each file and add it to the zip archive
        for user in participants:
            for index, image in enumerate(training_session.get_photostory_user_image_paths(user)):
                zip_file.write(
                    image,
                    image_name % {
                        'training_name': training_session.training.name,
                        'date': training_session.start_date.date(),
                        'session_uuid': training_session.id,
                        'trainee': user.get_full_name().replace(' ', '_'),
                        'index': index
                    }
                )

        zip_file.close()

        zip_data = zip_buffer.getvalue()
        zip_filename = self.get_slugified_filename()

        response_kwargs.setdefault('content_type', self.content_type)
        response = self.response_class(zip_data, **response_kwargs)
        response['Content-Disposition'] = 'attachment; filename=%s.zip' % zip_filename

        return response


@login_required
def session_users_import(request):
    if request.method == 'POST':
        pass
    else:
        pass

    return render_to_response('trainings/session_import.html',
                              locals(), context_instance=RequestContext(request))


@login_required
def session_trainee_content(request, session_uuid, trainee_uuid):
    session = get_object_or_404(TrainingSession, pk=session_uuid)
    trainee = get_object_or_404(get_user_model(), pk=trainee_uuid)

    # 1. Fetch all elements to download.
    documents = Document.objects.filter(
        hidden=False,
        is_deleted=False,
        trainer=False,
        training=session.training,
        user=session.trainer,
    )

    metaplans = Metaplan.objects.filter(training_session=session)

    try:
        free_note = trainee.note_set.get(document=None, training_session=session)
    except Note.DoesNotExist:
        free_note = None

    # 2. Zip all these elements.
    zip_buffer = io.BytesIO()
    zfile = zipfile.ZipFile(zip_buffer, 'w')

    # Add annotated .pdf documents.
    for document in documents:
        file_path = _generate_document(document, trainee)
        file_path = os.path.join(settings.MEDIA_ROOT, file_path)
        zfile.write(file_path, '%s.%s' % (
            document.name,
            os.path.basename(file_path).rsplit('.', 1)[1]
        ))

    # Add metaplans.
    for metaplan in metaplans:
        _html = render_to_string('cms/metaplans/metaplan_export.html', {'metaplan': metaplan})
        metaplan_title = metaplan.title.replace(' ', '_')[:25]
        zfile.writestr('metaplan/%s.html' % (metaplan_title), _html.encode('utf-8'))

    # Add notes.
    if free_note:
        zfile.writestr('notes.txt', free_note.text.encode('utf-8'))

    zfile.close()

    # 3. Send the .zip file.
    zip_data = zip_buffer.getvalue()
    zip_filename = '%s-%s.zip' % (session.training.name, trainee.username)
    zip_filename = zip_filename.replace(' ', '-')

    resp = HttpResponse(zip_data)
    resp['Content-Type'] = 'application/x-zip-compressed'
    resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename

    # Log downloads for all the documents contained in the .zip file.
    for document in documents:
        event_log(subject=document, sender=request.user, action=EVENT_ACTIONS.DOCUMENT_DOWNLOAD)

    return resp


# Helper
# ------

def _generate_document(document, user):
    notes = Note.objects.filter(user=user, document=document)

    if len(notes) == 0:
        return document.doc_file.orig_file.name

    annot = render_to_string('formation/scripts/annotation.txt', locals())
    annot_file_name = os.path.join(settings.ANNOT_SCRIPT_ROOT, 'annot_%s.txt' % (document.id))

    with codecs.open(annot_file_name, 'w', 'iso-8859-1') as file:
        file.write(annot.encode('ascii', 'ignore'))

    output_file_name = '%s-%s.pdf' % (document.id, user.id)
    output_path = os.path.join(settings.ANNOT_DATA_ROOT, output_file_name)
    if not os.path.exists(output_path):
        command = 'gs -o %s -sDEVICE=pdfwrite -dPDFSETTINGS=/prepress %s %s' % (
            output_path,
            os.path.join(settings.MEDIA_ROOT, document.doc_file.orig_file.path),
            annot_file_name,
        )
        call(['cat', '%s' % annot_file_name])
        call(command.split(' '))
        sleep(1)

    return output_path


# pylint: disable=abstract-method
class ActivityQRCodeDownloadMixin(QRCodeDownloadMixin, TrainingActivityBase):
    """Mixin containing specific methods and attributes to download activity launch QRCode."""

    qrcode_class = ActivityQRCode


class ActivityQRCodeDownloadDetailView(ActivityQRCodeDownloadMixin, ObjectQRCodeDownloadDetailView):
    """View to download QrCode able to launch an activity."""


class ActivityQRCodeDownloadListView(ActivityQRCodeDownloadMixin, ObjectQRCodeDownloadListView):
    """View to download training QrCodes able to launch an activity."""

    def get_queryset(self):
        """Get training activities.

        Only Document, Evaluation, Workshop, Roleplay.
        We can't launch Template with a QRCode like BrainstormerTemplate and MetaplanTemplate.

        """
        return super().get_queryset().instance_of(
            Document, Evaluation, Workshop, Roleplay
        )

    def get_filename(self):
        """Get the filename used to name the generated zip file."""
        return str(self.training)


class SessionTokenView(TrainingSessionBaseView, generic.DetailView):
    """View to display the selected session token."""

    template_name = 'trainings/trainingsession_token.html'
