uclouvain/osis-dissertation

View on GitHub
views/proposition_dissertation.py

Summary

Maintainability
D
2 days
Test Coverage
##############################################################################
#
#    OSIS stands for Open Student Information System. It's an application
#    designed to manage the core business of higher education institutions,
#    such as universities, faculties, institutes and professional schools.
#    The core business involves the administration of students, teachers,
#    courses, programs and so on.
#
#    Copyright (C) 2015-2019 Université catholique de Louvain (http://www.uclouvain.be)
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    A copy of this license - GNU General Public License - is available
#    at the root of the source code of this program.  If not,
#    see http://www.gnu.org/licenses/.
#
##############################################################################
import re
import time

from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.mixins import UserPassesTestMixin
from django.db import models
from django.db.models import Count
from django.db.models import Q, F, ExpressionWrapper, OuterRef, Subquery, Prefetch
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.views.generic import CreateView
from openpyxl import Workbook

from base import models as mdl
from base.models import academic_year
from base.models.education_group_year import EducationGroupYear
from base.views.mixins import AjaxTemplateMixin
from dissertation.forms import PropositionDissertationForm, ManagerPropositionDissertationForm, \
    ManagerPropositionRoleForm, ManagerPropositionDissertationEditForm, PropositionDissertationFileForm
from dissertation.models import adviser, offer_proposition, offer_proposition_group
from dissertation.models import dissertation, proposition_dissertation, proposition_role, \
    proposition_offer
from dissertation.models.dissertation import Dissertation
from dissertation.models.enums import dissertation_role_status
from dissertation.models.enums import dissertation_status
from dissertation.models.enums.dissertation_role_status import DissertationRoleStatus
from dissertation.models.offer_proposition import OfferProposition
from dissertation.models.proposition_dissertation import PropositionDissertation
from dissertation.models.proposition_offer import PropositionOffer
from dissertation.models.proposition_role import PropositionRole
from dissertation.perms import user_is_proposition_promotor, \
    adviser_can_manage_proposition_dissertation, autorized_proposition_dissert_promotor_or_manager_or_author
from osis_common.document.xls_build import save_virtual_workbook

MAX_PROPOSITION_ROLE = 4


def detect_in_request(request, wanted_key, wanted_value):
    for key in request.POST:
        if wanted_key in key and request.POST[key] == wanted_value:
            return True
    return False


def edit_proposition(form, proposition_offers, request):
    proposition = form.save()
    for old in proposition_offers:
        old.delete()
    generate_proposition_offers(request, proposition)
    return proposition


def create_proposition(form, person, request):
    proposition = form.save()
    proposition.set_creator(person)
    generate_proposition_offers(request, proposition)
    return proposition


def generate_proposition_offers(request, proposition):
    for key, value in request.POST.items():
        if 'txt_checkbox_' in key and value == 'on':
            offer = PropositionOffer()
            offer.proposition_dissertation = proposition
            offer_proposition_id = key.replace("txt_checkbox_", "")
            offer.offer_proposition = offer_proposition.find_by_id(int(offer_proposition_id))
            offer.save()


def is_valid(request, form):
    return form.is_valid() and detect_in_request(request, 'txt_checkbox_', 'on')


def return_prefetch_propositions():
    current_academic_year = academic_year.current_academic_year()
    return Prefetch(
        "offer_propositions",
        queryset=OfferProposition.objects.annotate(last_acronym=Subquery(
            EducationGroupYear.objects.filter(
                education_group__offer_proposition=OuterRef('pk'),
                academic_year=current_academic_year).values('acronym')[:1]
        ))
    )


###########################
#      MANAGER VIEWS      #
###########################

def _append_dissertations_count(prop):
    prop.dissertations_count = dissertation.count_by_proposition(prop)

    return prop


@login_required
@user_passes_test(adviser.is_manager)
def manager_proposition_dissertation_delete(request, pk):
    proposition = proposition_dissertation.find_by_id(pk)
    if proposition is None:
        return redirect('manager_proposition_dissertations')
    proposition.deactivate()
    return redirect('manager_proposition_dissertations')


@login_required
@user_passes_test(adviser.is_manager)
def manager_proposition_dissertation_detail(request, pk):
    prefetch_propositions = return_prefetch_propositions()
    proposition = get_object_or_404(PropositionDissertation.objects.select_related('author__person', 'creator').
                                    prefetch_related(prefetch_propositions,
                                                     'offer_propositions__education_group__educationgroupyear_set',
                                                     'propositionrole_set__adviser__person',
                                                     ), pk=pk)
    adv = request.user.person.adviser
    count_use = dissertation.count_by_proposition(proposition)
    percent = count_use * 100 / proposition.max_number_student if proposition.max_number_student else 0
    count_proposition_role = proposition_role.count_by_proposition(proposition)
    proposition_dissertation_file_form = PropositionDissertationFileForm(
        request.POST or None,
        instance=proposition
    )
    if request.method == 'POST' and proposition_dissertation_file_form.is_valid():
        proposition_dissertation_file_form.save()
        return redirect('proposition_dissertation_detail', pk=proposition.pk)
    proposition_dissertation_file_form = PropositionDissertationFileForm(
        instance=proposition
    )

    check_authorisation_of_proposition = autorized_proposition_dissert_promotor_or_manager_or_author(
        request.user,
        proposition
    )
    if count_proposition_role < 1:
        proposition_role.add(DissertationRoleStatus.PROMOTEUR.name, proposition.author, proposition)
    return render(request, 'manager_proposition_dissertation_detail.html',
                  {
                      'proposition_dissertation': proposition,
                      'adviser': adv,
                      'count_use': count_use,
                      'percent': round(percent, 2),
                      'check_authorisation_of_proposition': check_authorisation_of_proposition,
                      'count_proposition_role': count_proposition_role,
                      'proposition_dissertation_file_form': proposition_dissertation_file_form,
                      'document': proposition_dissertation_file_form.initial['proposition_dissertation_file']
                  })


@login_required
@user_passes_test(adviser.is_manager)
def manage_proposition_dissertation_edit(request, pk):
    proposition = get_object_or_404(PropositionDissertation.objects.select_related('author__person', 'creator').
                                    prefetch_related('offer_propositions__education_group__educationgroupyear_set',
                                                     'propositionrole_set__adviser__person'), pk=pk)
    offer_propositions = OfferProposition.objects.select_related('offer_proposition_group').order_by('acronym')
    offer_propositions_group = offer_proposition_group.find_all_ordered_by_name_short()
    offer_propositions_error = None
    proposition_offers = proposition.propositionoffer_set.all()
    if request.method == "POST":
        form = ManagerPropositionDissertationEditForm(request.POST, instance=proposition)
        if is_valid(request, form):
            proposition = edit_proposition(form, proposition_offers, request)
            return redirect('manager_proposition_dissertation_detail', pk=proposition.pk)
        if not detect_in_request(request, 'txt_checkbox_', 'on'):
            offer_propositions_error = 'select_at_least_one_item'
    else:
        form = ManagerPropositionDissertationEditForm(instance=proposition)
    return render(request, 'manager_proposition_dissertation_edit.html',
                  {'prop_dissert': proposition,
                   'form': form,
                   'author': proposition.author,
                   'types_choices': PropositionDissertation.TYPES_CHOICES,
                   'levels_choices': PropositionDissertation.LEVELS_CHOICES,
                   'collaborations_choices': PropositionDissertation.COLLABORATION_CHOICES,
                   'offer_propositions': offer_propositions,
                   'offer_propositions_error': offer_propositions_error,
                   'offer_proposition_group': offer_propositions_group,
                   'proposition_offers': proposition_offers})


@login_required
@user_passes_test(adviser.is_manager)
def manager_proposition_dissertations_jury_edit(request, pk):
    prop_role = proposition_role.get_by_id(pk)
    if prop_role is None:
        return redirect('manager_proposition_dissertations')
    proposition = prop_role.proposition_dissertation
    return redirect('manager_proposition_dissertation_detail', pk=proposition.pk)


class PropositionDissertationJuryNewView(AjaxTemplateMixin, UserPassesTestMixin, CreateView):
    model = PropositionRole
    template_name = 'proposition_dissertations_jury_edit_inner.html'
    form_class = ManagerPropositionRoleForm
    raise_exception = True

    def test_func(self):
        return proposition_role.count_by_proposition(self.proposition) < MAX_PROPOSITION_ROLE

    def dispatch(self, request, *args, **kwargs):
        adv = get_current_adviser(request)
        if adviser_can_manage_proposition_dissertation(self.proposition, adv) \
                or user_is_proposition_promotor(request.user, self.proposition.pk) \
                or self.proposition.creator == adv.person:
            return super().dispatch(request, *args, **kwargs)
        return redirect('proposition_dissertations')

    @cached_property
    def proposition(self):
        return get_object_or_404(proposition_dissertation.PropositionDissertation, pk=self.kwargs['pk'])

    def get_initial(self):
        return {'status': dissertation_role_status, 'proposition_dissertation': self.proposition}

    def get_context_data(self, **kwargs):
        context = super(PropositionDissertationJuryNewView, self).get_context_data(**kwargs)
        context['manager'] = adviser.is_manager(self.request.user)
        return context

    def get_success_url(self):
        return None


@login_required
@user_passes_test(adviser.is_manager)
def manager_proposition_dissertations_role_delete(request, pk):
    prop_role = proposition_role.get_by_id(pk)
    if prop_role is None:
        return redirect('manager_proposition_dissertations')
    proposition = prop_role.proposition_dissertation
    prop_role.delete()
    return redirect('manager_proposition_dissertation_detail', pk=proposition.pk)


@login_required
@user_passes_test(adviser.is_manager)
def manager_proposition_dissertation_new(request):
    offer_propositions = OfferProposition.objects.exclude(
        education_group=None
    ).select_related('offer_proposition_group', 'education_group')
    offer_propositions_group = offer_proposition_group.find_all_ordered_by_name_short()
    offer_propositions_error = None
    if request.method == "POST":
        form = ManagerPropositionDissertationForm(request.POST)
        if is_valid(request, form):
            person = mdl.person.find_by_user(request.user)
            proposition = create_proposition(form, person, request)
            return redirect('manager_proposition_dissertation_detail', pk=proposition.pk)
        else:
            offer_propositions_error = 'select_at_least_one_item'
    else:
        form = ManagerPropositionDissertationForm(initial={'active': True})

    return render(request, 'manager_proposition_dissertation_new.html',
                  {'form': form,
                   'types_choices': PropositionDissertation.TYPES_CHOICES,
                   'levels_choices': PropositionDissertation.LEVELS_CHOICES,
                   'collaborations_choices': PropositionDissertation.COLLABORATION_CHOICES,
                   'offer_propositions_error': offer_propositions_error,
                   'offer_propositions': offer_propositions,
                   'offer_proposition_group': offer_propositions_group})


@login_required
@user_passes_test(adviser.is_manager)
def manager_proposition_dissertations(request):
    current_academic_year = academic_year.current_academic_year()
    prefetch_propositions = return_prefetch_propositions()
    if 'search' in request.GET:
        terms = request.GET['search']
        propositions_dissertations = _search_proposition_dissertation(current_academic_year, prefetch_propositions,
                                                                      request, terms)
    else:
        propositions_dissertations = _get_all_proposition_by_offer(current_academic_year, prefetch_propositions,
                                                                   request)

    if 'bt_xlsx' in request.GET:
        return _export_proposition_dissertation_xlsx(propositions_dissertations)

    else:
        return render(request, "manager_proposition_dissertations_list.html",
                      {'propositions_dissertations': propositions_dissertations})


def _export_proposition_dissertation_xlsx(propositions_dissertations):
    filename = "EXPORT_propositions_{}.xlsx".format(time.strftime("%Y-%m-%d_%H:%M"))
    workbook = Workbook()
    worksheet1 = workbook.active
    worksheet1.title = "proposition_dissertation"
    worksheet1.append(['Date_de_création', 'Teacher', 'Title',
                       'Type', 'Level', 'Collaboration', 'Maximum number of places', 'Places Remaining',
                       'Visibility', 'Active', 'Programme(s)', 'Description'])
    types_choices = dict(PropositionDissertation.TYPES_CHOICES)
    levels_choices = dict(PropositionDissertation.LEVELS_CHOICES)
    collaboration_choices = dict(PropositionDissertation.COLLABORATION_CHOICES)
    for proposition in propositions_dissertations:
        education_groups = ""
        for offer_prop in proposition.offer_propositions.all():
            education_groups += "{}, ".format(str(offer_prop.last_acronym))
        worksheet1.append([proposition.created_date,
                           str(proposition.author),
                           proposition.title,
                           str(types_choices[proposition.type]),
                           str(levels_choices[proposition.level]),
                           str(collaboration_choices[proposition.collaboration]),
                           proposition.max_number_student,
                           proposition.remaining_places if
                           proposition.remaining_places > 0 else 0,
                           proposition.visibility,
                           proposition.active,
                           education_groups,
                           re.sub(r'[\000-\010]|[\013-\014]|[\016-\037]', '', proposition.description)
                           ])
    response = HttpResponse(
        save_virtual_workbook(workbook),
        content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=binary')
    response['Content-Disposition'] = "%s%s" % ("attachment; filename=", filename)
    return response


def _get_all_proposition_by_offer(current_academic_year, prefetch_propositions, request):
    propositions_dissertations = PropositionDissertation.objects.filter(
        offer_propositions__education_group__facultyadviser__adviser__person__user=request.user,
        active=True
    ).annotate(dissertations_count=Count(
        'dissertations',
        filter=Q(
            active=True,
            dissertations__education_group_year__academic_year=current_academic_year
        ) & ~Q(dissertations__status__in=(dissertation_status.DRAFT, dissertation_status.DIR_KO))
    )).annotate(
        remaining_places=ExpressionWrapper(
            F('max_number_student') - F('dissertations_count'),
            output_field=models.IntegerField()
        )
    ).select_related('author__person', 'creator').prefetch_related(prefetch_propositions)
    return propositions_dissertations


def _search_proposition_dissertation(current_academic_year, prefetch_propositions, request, terms):
    propositions_dissertations = PropositionDissertation.objects.filter(
        offer_propositions__education_group__facultyadviser__adviser__person__user=request.user,
        active=True
    ).filter(
        Q(title__icontains=terms) |
        Q(description__icontains=terms) |
        Q(author__person__first_name__icontains=terms) |
        Q(author__person__middle_name__icontains=terms) |
        Q(author__person__last_name__icontains=terms) |
        Q(propositionoffer__offer_proposition__acronym__icontains=terms)).annotate(
        dissertations_count=Count(
            'dissertations',
            filter=Q(
                active=True,
                dissertations__education_group_year__academic_year=current_academic_year
            ) & ~Q(dissertations__status__in=(dissertation_status.DRAFT, dissertation_status.DIR_KO))
        )).annotate(
        remaining_places=ExpressionWrapper(
            F('max_number_student') - F('dissertations_count'),
            output_field=models.IntegerField()
        )).select_related('author__person', 'creator').prefetch_related(prefetch_propositions)
    return propositions_dissertations


###########################
#      TEACHER VIEWS      #
###########################


def get_current_adviser(request):
    person = mdl.person.find_by_user(request.user)
    return adviser.search_by_person(person)


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertations(request):
    prefetch_propositions = return_prefetch_propositions()
    propositions_dissertations = PropositionDissertation.objects.filter(
        active=True,
    ).select_related('author__person', 'creator').prefetch_related(prefetch_propositions)
    return render(request,
                  'proposition_dissertations_list.html',
                  {'propositions_dissertations': propositions_dissertations}
                  )


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertation_delete(request, pk):
    proposition = proposition_dissertation.find_by_id(pk)
    if proposition is None:
        return redirect('proposition_dissertations')
    proposition.deactivate()
    return redirect('proposition_dissertations')


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertation_detail(request, pk):
    current_academic_year = academic_year.current_academic_year()

    prefetch_disserts = Prefetch(
        "dissertations",
        queryset=Dissertation.objects.filter(
            active=True
        ).filter(
            education_group_year__academic_year=current_academic_year
        ).exclude(status__in=(dissertation_status.DRAFT, dissertation_status.DIR_KO))
    )
    prefetch_offer_propositions = return_prefetch_propositions()

    proposition = get_object_or_404(
        PropositionDissertation.objects.select_related(
            'author__person', 'creator'
        ).prefetch_related(
            prefetch_offer_propositions, prefetch_disserts, 'propositionrole_set__adviser__person'
        ), pk=pk
    )
    if request.method == "POST":
        proposition_dissertation_file_form = PropositionDissertationFileForm(
            request.POST,
            instance=proposition
        )
        if proposition_dissertation_file_form.is_valid():
            proposition_dissertation_file_form.save()
            return redirect('proposition_dissertation_detail', pk=proposition.pk)
    else:
        proposition_dissertation_file_form = PropositionDissertationFileForm(
            instance=proposition
        )
    check_authorisation_of_proposition = autorized_proposition_dissert_promotor_or_manager_or_author(
        request.user,
        proposition
    )
    offer_propositions = proposition.offer_propositions.all()
    count_use = proposition.dissertations.all().count()
    percent = count_use * 100 / proposition.max_number_student if proposition.max_number_student else 0
    count_proposition_role = proposition.propositionrole_set.all().count()
    if count_proposition_role < 1:
        proposition_role.add(DissertationRoleStatus.PROMOTEUR.name, proposition.author, proposition)
    proposition_roles = PropositionRole.objects.filter(proposition_dissertation=proposition) \
        .select_related('adviser__person')
    return render(
        request,
        'proposition_dissertation_detail.html',
        {
            'proposition_dissertation': proposition,
            'check_authorisation_of_proposition': check_authorisation_of_proposition,
            'offer_propositions': offer_propositions,
            'adviser': get_current_adviser(request),
            'count_use': count_use,
            'percent': round(percent, 2),
            'proposition_roles': proposition_roles,
            'count_proposition_role': count_proposition_role,
            'proposition_dissertation_file_form': proposition_dissertation_file_form,
            'document': proposition_dissertation_file_form.initial['proposition_dissertation_file']
        }
    )


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertation_edit(request, pk):
    proposition = proposition_dissertation.find_by_id(pk)
    if proposition is None:
        return redirect('proposition_dissertations')
    adv = get_current_adviser(request)
    offer_propositions = offer_proposition.find_all_ordered_by_acronym()
    offer_propositions_group = offer_proposition_group.find_all_ordered_by_name_short()
    offer_propositions_error = None
    proposition_offers = proposition_offer.find_by_proposition_dissertation(proposition)
    if proposition.author == adv or proposition.creator == adv.person:
        if request.method == "POST":
            form = PropositionDissertationForm(request.POST, instance=proposition)
            if is_valid(request, form):
                proposition = edit_proposition(form, proposition_offers, request)
                return redirect('proposition_dissertation_detail', pk=proposition.pk)
            if not detect_in_request(request, 'txt_checkbox_', 'on'):
                offer_propositions_error = 'select_at_least_one_item'
                proposition_offers = None

        form = PropositionDissertationForm(instance=proposition)
        return render(request, 'proposition_dissertation_edit.html',
                      {'prop_dissert': proposition,
                       'form': form,
                       'types_choices': PropositionDissertation.TYPES_CHOICES,
                       'levels_choices': PropositionDissertation.LEVELS_CHOICES,
                       'collaborations_choices': PropositionDissertation.COLLABORATION_CHOICES,
                       'offer_propositions': offer_propositions,
                       'offer_propositions_error': offer_propositions_error,
                       'proposition_offers': proposition_offers,
                       'offer_proposition_group': offer_propositions_group
                       })
    else:
        return redirect('proposition_dissertation_detail', pk=proposition.pk)


@login_required
@user_passes_test(adviser.is_teacher)
def my_dissertation_propositions(request):
    prefetch_propositions = return_prefetch_propositions()
    current_academic_year = academic_year.current_academic_year()
    propositions_dissertations = PropositionDissertation.objects.filter(
        active=True,
        author=request.user.person.adviser
    ).annotate(
        dissertations_count=Count(
            'dissertations',
            filter=Q(
                active=True,
                dissertations__education_group_year__academic_year=current_academic_year
            ) & ~Q(dissertations__status__in=(dissertation_status.DRAFT, dissertation_status.DIR_KO))
        )
    ).annotate(
        remaining_places=ExpressionWrapper(
            F('max_number_student') - F('dissertations_count'),
            output_field=models.IntegerField()
        )
    ).select_related('author__person', 'creator').prefetch_related(prefetch_propositions)
    return render(request, 'proposition_dissertations_list_my.html',
                  {'propositions_dissertations': propositions_dissertations})


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertations_created(request):
    propositions_dissertations = proposition_dissertation.get_created_for_teacher(get_current_adviser(request))
    return render(request, 'proposition_dissertations_list_created.html',
                  {'propositions_dissertations': propositions_dissertations})


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertation_new(request):
    current_ac_year = academic_year.current_academic_year()
    perso = request.user.person
    offer_propositions = OfferProposition.objects.exclude(education_group=None).annotate(last_acronym=Subquery(
        EducationGroupYear.objects.filter(
            education_group__offer_proposition=OuterRef('pk'),
            academic_year=current_ac_year).values('acronym')[:1]
    )).select_related('offer_proposition_group').order_by('last_acronym')
    offer_propositions_group = offer_proposition_group.find_all_ordered_by_name_short()
    offer_propositions_error = None
    if request.method == "POST":
        form = PropositionDissertationForm(request.POST)
        if is_valid(request, form):
            proposition = create_proposition(form, perso, request)
            return redirect('proposition_dissertation_detail', pk=proposition.pk)
        else:
            offer_propositions_error = 'select_at_least_one_item'
    else:
        form = PropositionDissertationForm(initial={'author': get_current_adviser(request), 'active': True})

    return render(request, 'proposition_dissertation_new.html',
                  {'form': form,
                   'types_choices': PropositionDissertation.TYPES_CHOICES,
                   'levels_choices': PropositionDissertation.LEVELS_CHOICES,
                   'collaborations_choices': PropositionDissertation.COLLABORATION_CHOICES,
                   'offer_propositions_error': offer_propositions_error,
                   'offer_propositions': offer_propositions,
                   'offer_proposition_group': offer_propositions_group})


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertations_search(request):
    prefetch_propositions = return_prefetch_propositions()
    propositions_dissertations = proposition_dissertation.search(terms=request.GET['search'],
                                                                 active=True,
                                                                 visibility=True) \
        .select_related('author__person', 'creator').prefetch_related(prefetch_propositions)
    return render(request, "proposition_dissertations_list.html",
                  {'propositions_dissertations': propositions_dissertations})


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertations_jury_edit(request, pk):
    prop_role = proposition_role.get_by_id(pk)
    if prop_role is None:
        return redirect('proposition_dissertations')
    proposition = prop_role.proposition_dissertation
    return redirect('proposition_dissertation_detail', pk=proposition.pk)


@login_required
@user_passes_test(adviser.is_teacher)
def proposition_dissertations_role_delete(request, pk):
    prop_role = proposition_role.get_by_id(pk)
    if prop_role is None:
        return redirect('proposition_dissertations')
    proposition = prop_role.proposition_dissertation
    adv = get_current_adviser(request)

    if prop_role.status != 'PROMOTEUR' and (proposition.author == adv or proposition.creator == adv.person):
        prop_role.delete()

    return redirect('proposition_dissertation_detail', pk=proposition.pk)