Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IUFC-758 #567

Open
wants to merge 10 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 0 additions & 35 deletions business/prospect.py

This file was deleted.

32 changes: 21 additions & 11 deletions business/xls/xls_prospect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# 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)
# Copyright (C) 2015-2022 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
Expand All @@ -23,27 +23,37 @@
# see http://www.gnu.org/licenses/.
#
##############################################################################
from typing import List

from django.utils.translation import gettext_lazy as _
from openpyxl.styles import Font

from base.business.xls import get_name_or_username
from continuing_education.business.prospect import get_prospects_by_user
from continuing_education.forms.search import ProspectFilterForm
from continuing_education.models.prospect import Prospect
from osis_common.document import xls_build
from program_management.views.search import get_filter

XLS_DESCRIPTION = _('Prospects list')
XLS_FILENAME = _('Prospects_list')
WORKSHEET_TITLE = _('Prospects list')
BOLD_FONT = Font(bold=True)


def create_xls(user):
prospects_list = get_prospects_by_user(user)
def create_xls(user, prospects_list: List[Prospect], prospect_filter_form: ProspectFilterForm):
working_sheets_data = _prepare_xls_content(prospects_list)
parameters = {xls_build.DESCRIPTION: XLS_DESCRIPTION,
xls_build.USER: get_name_or_username(user),
xls_build.FILENAME: XLS_FILENAME,
xls_build.HEADER_TITLES: _get_titles(),
xls_build.WS_TITLE: WORKSHEET_TITLE}

return xls_build.generate_xls(xls_build.prepare_xls_parameters_list(working_sheets_data, parameters))
parameters = {
xls_build.DESCRIPTION: XLS_DESCRIPTION,
xls_build.USER: get_name_or_username(user),
xls_build.FILENAME: XLS_FILENAME,
xls_build.HEADER_TITLES: _get_titles(),
xls_build.WS_TITLE: WORKSHEET_TITLE,
xls_build.FONT_ROWS: {BOLD_FONT: [0]},
}
return xls_build.generate_xls(
list_parameters=xls_build.prepare_xls_parameters_list(working_sheets_data, parameters),
filters=get_filter(prospect_filter_form)
)


def _prepare_xls_content(prospect_list):
Expand Down
44 changes: 44 additions & 0 deletions forms/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ADMISSION_STATE_CHOICES
from continuing_education.models.enums.admission_state_choices import REJECTED, SUBMITTED, WAITING, ACCEPTED, \
REGISTRATION_SUBMITTED, VALIDATED, STATE_CHOICES, ARCHIVE_STATE_CHOICES, DRAFT, ACCEPTED_NO_REGISTRATION_REQUIRED
from continuing_education.models.prospect import Prospect

STATE_TO_DISPLAY = [SUBMITTED, REJECTED, WAITING, DRAFT, ACCEPTED_NO_REGISTRATION_REQUIRED]
STATE_FOR_REGISTRATION = [ACCEPTED, REGISTRATION_SUBMITTED, VALIDATED]
Expand Down Expand Up @@ -447,3 +448,46 @@ def get_managers(self):
).values_list('person__id')
)
return qs


class ProspectFilterForm(CommonFilterForm):

def __init__(self, data, user=None, *args, **kwargs):
super(ProspectFilterForm, self).__init__(data, *args, **kwargs)
self.prospects_queryset = self.get_prospects_by_user(user)
self._build_prospects_formation_choices()

def get_prospects_by_user(self, user):
return Prospect.objects.filter(
formation__continuingeducationtrainingmanager__person=user.person
).select_related(
'formation__education_group'
).prefetch_related(
'formation__education_group__educationgroupyear_set',
'formation__continuingeducationtrainingmanager_set'
)

def get_propects_with_filter(self):
qs = self.prospects_queryset
formation = self.cleaned_data.get('formation')
free_text = self.cleaned_data.get('free_text')
if formation:
qs = qs.filter(formation=formation)

if free_text:
qs = qs.filter(
Q(first_name__unaccent__icontains=free_text) |
Q(name__unaccent__icontains=free_text) |
Q(email__icontains=free_text) |
Q(phone_number__icontains=free_text) |
Q(formation__education_group__educationgroupyear__acronym__icontains=free_text) |
Q(formation__education_group__educationgroupyear__title__unaccent__icontains=free_text) |
Q(postal_code__icontains=free_text) |
Q(city__unaccent__icontains=free_text)
).distinct()
return qs

def _build_prospects_formation_choices(self):
self.fields['formation'].queryset = ContinuingEducationTraining.objects.formations().filter(
id__in=self.prospects_queryset.values_list('formation', flat=False)
).distinct().order_by('education_group__educationgroupyear__acronym')
32 changes: 30 additions & 2 deletions templates/continuing_education/prospects.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ <h2>{% trans 'Prospects' %}</h2>
</div>

<div class="panel panel-default">
<div class="panel panel-body">
<form style="display: inline;" action="{% url 'prospects' %}" method="get" class="" id="search_form">
{% csrf_token %}
<div class="row">
<div class="col-md-5">
<label for="formation_id">{% trans 'Formation' %}</label>
{{ search_form.formation }}
</div>
<div class="col-md-5">
<label for="free_text_id">{% trans 'In all fields' %}</label>
{{ search_form.free_text }}
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary" style="margin-top: 26px;">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
{% include 'blocks/button/button_filter_refresh.html' %}
</div>
</div>
{% include 'continuing_education/blocks/button/xls_hidden.html' %}
</form>
</div>
<br>
{% if prospects %}
<div class="dropdown text-right" style="margin-right: 10px;">
Expand All @@ -59,7 +81,7 @@ <h2>{% trans 'Prospects' %}</h2>
</a>
</li>
<li>
<a id="btn_produce_xls" style="margin-right:10px;" href="{% url 'prospects_xls' %}"
<a id="btn_produce_prospects_xls" style="margin-right:10px;"
title="Produce xls" class="download">
&nbsp;{% trans 'Produce xls' %}
</a>
Expand Down Expand Up @@ -96,7 +118,7 @@ <h2>{% trans 'Prospects' %}</h2>
</thead>
<tfoot>
<tr>
<td colspan="6">
<td colspan="7">
{% bootstrap_pagination prospects extra=request.GET.urlencode %}
</td>
</tr>
Expand Down Expand Up @@ -152,5 +174,11 @@ <h2>{% trans 'Prospects' %}</h2>
$("#prospects_form").attr("action", url);
$("#prospects_form").submit();
});

$("#btn_produce_prospects_xls").click(function(e) {
prepare_xls(e, 'xls_prospects');
});


</script>
{% endblock %}
37 changes: 25 additions & 12 deletions tests/business/test_prospect.py → tests/forms/test_prospect.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
##############################################################################
#
# OSIS stands for Open Student Information System. It's an application
# 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-2021 Université catholique de Louvain (http://www.uclouvain.be)
# Copyright (C) 2015-2022 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
Expand All @@ -23,35 +23,48 @@
# see http://www.gnu.org/licenses/.
#
##############################################################################
from unittest import mock

from django.test import TestCase

from base.tests.factories.academic_year import create_current_academic_year
from base.tests.factories.education_group import EducationGroupFactory
from base.tests.factories.education_group_year import EducationGroupYearFactory
from continuing_education.business.prospect import get_prospects_by_user
from continuing_education.forms.search import ProspectFilterForm
from continuing_education.tests.factories.continuing_education_training import ContinuingEducationTrainingFactory
from continuing_education.tests.factories.prospect import ProspectFactory
from continuing_education.tests.factories.roles.continuing_education_training_manager import \
ContinuingEducationTrainingManagerFactory


class TestProspect(TestCase):
def test_get_prospects_by_user(self):
@classmethod
def setUpTestData(cls):
academic_year = create_current_academic_year()
education_group = EducationGroupFactory()
EducationGroupYearFactory(
education_group=education_group,
academic_year=academic_year
)
training_1 = ContinuingEducationTrainingFactory(education_group=education_group)
manager = ContinuingEducationTrainingManagerFactory(training=training_1)
training_1 = ContinuingEducationTrainingFactory(education_group=education_group, active=True)
cls.manager = ContinuingEducationTrainingManagerFactory(training=training_1)
cls.prospect_1 = ProspectFactory(formation=training_1, name="Delwart")
cls.prospect_2 = ProspectFactory(formation=training_1, name="Debouche")

prospect_1 = ProspectFactory(formation=training_1)
prospect_2 = ProspectFactory(formation=training_1)
ProspectFactory()
ProspectFactory()
@mock.patch('continuing_education.forms.search._build_prospects_formation_choices')
def test_get_prospects_by_user(self, mock_formation_dropdown):
form = ProspectFilterForm(data={}, user=self.manager.person.user)
self.assertTrue(form.is_valid())
self.assertCountEqual(
form.get_propects_with_filter(),
[self.prospect_1, self.prospect_2]
)

@mock.patch('continuing_education.forms.search._build_prospects_formation_choices')
def test_get_prospects_by_user_and_free_text(self, mock_formation_dropdown):
form = ProspectFilterForm(data={'free_text': 'wart'}, user=self.manager.person.user)
self.assertTrue(form.is_valid())
self.assertCountEqual(
get_prospects_by_user(manager.person.user),
[prospect_1, prospect_2]
form.get_propects_with_filter(),
[self.prospect_1]
)
3 changes: 2 additions & 1 deletion tests/views/test_prospect.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,13 @@ class ProspectListTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.manager = ContinuingEducationTrainingManagerFactory()
cls.academic_year = create_current_academic_year()

def setUp(self):
self.client.force_login(self.manager.person.user)

def test_prospect_list_ordered_by_formation(self):
self.academic_year = create_current_academic_year()

self.education_groups = [EducationGroupFactory() for _ in range(1, 3)]

acronyms = ['AAA', 'BBA', 'CAA']
Expand Down
1 change: 0 additions & 1 deletion urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
url(r'^prospects/', include([
url(r'^$', prospect.list_prospects, name='prospects'),
url(r'^(?P<prospect_id>[0-9]+)/', prospect.prospect_details, name='prospect_details'),
url(r'^reporting', prospect.prospect_xls, name='prospects_xls'),
url(r'^delete', prospect.delete_prospects, name='prospects_delete'),
])),
url(r'^tasks/', include([
Expand Down
26 changes: 19 additions & 7 deletions views/prospect.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,35 @@
##############################################################################

from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect, reverse, get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse
from rules.contrib.views import permission_required, objectgetter

from base.views.common import display_error_messages, display_success_messages
from continuing_education.business.prospect import get_prospects_by_user
from continuing_education.business.xls.xls_prospect import create_xls
from continuing_education.forms.search import ProspectFilterForm
from continuing_education.models.prospect import Prospect
from continuing_education.views.common import get_object_list
from django.utils.translation import gettext_lazy as _


@login_required
@permission_required('continuing_education.view_prospect', raise_exception=True)
def list_prospects(request):
prospects_list = get_prospects_by_user(request.user)
search_form = ProspectFilterForm(data=request.GET, user=request.user)

if search_form.is_valid():
prospects_list = search_form.get_propects_with_filter()
else:
prospects_list = Prospect.objects.none()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ne faut-il pas afficher un message d'erreur si le formulaire est non valide ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mathieuzen En fait dans le cadre des prospects, je pense que rien ne peut être invalide ....Donc je vois pas trop ce que je pourrais afficher???

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

search_form est toujours valide ? Si oui alors pas besoin de faire le else j'imagine...


if request.GET.get('xls_status') == "xls_prospects":
return prospect_xls(request, prospects_list, search_form)

return render(request, "continuing_education/prospects.html", {
'prospects': get_object_list(request, prospects_list),
'prospects_count': len(prospects_list)
'prospects_count': len(prospects_list),
'search_form': search_form
})


Expand All @@ -61,8 +72,9 @@ def prospect_details(request, prospect_id):

@login_required
@permission_required('continuing_education.export_prospect', raise_exception=True)
def prospect_xls(request):
return create_xls(request.user)
def prospect_xls(request, prospects_list, search_form: ProspectFilterForm):
return create_xls(request.user, prospects_list, search_form)



@login_required
Expand Down