Merge branch 'master' into Evarin/sitecof-improvements

This commit is contained in:
Martin Pépin 2020-05-08 15:56:42 +02:00
commit 67d7dafc14
27 changed files with 1144 additions and 1139 deletions

View file

@ -1,94 +1,56 @@
from django import shortcuts
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.http import Http404
from django.views.generic import TemplateView
from gestioncof.decorators import buro_required
from gestioncof.models import CofProfile
from shared.views import autocomplete
if getattr(settings, "LDAP_SERVER_URL", None):
from ldap3 import Connection
else:
# shared.tests.testcases.TestCaseMixin.mockLDAP needs
# Connection to be defined in order to mock it.
Connection = None
User = get_user_model()
class Clipper(object):
def __init__(self, clipper, fullname):
if fullname is None:
fullname = ""
assert isinstance(clipper, str)
assert isinstance(fullname, str)
self.clipper = clipper
self.fullname = fullname
class COFMemberSearch(autocomplete.ModelSearch):
model = User
search_fields = ["username", "first_name", "last_name"]
def __str__(self):
return "{} ({})".format(self.clipper, self.fullname)
def __eq__(self, other):
return self.clipper == other.clipper and self.fullname == other.fullname
def get_queryset_filter(self, *args, **kwargs):
qset_filter = super().get_queryset_filter(*args, **kwargs)
qset_filter &= Q(profile__is_cof=True)
return qset_filter
@buro_required
def autocomplete(request):
if "q" not in request.GET:
raise Http404
q = request.GET["q"]
data = {"q": q}
class COFOthersSearch(autocomplete.ModelSearch):
model = User
search_fields = ["username", "first_name", "last_name"]
queries = {}
bits = q.split()
def get_queryset_filter(self, *args, **kwargs):
qset_filter = super().get_queryset_filter(*args, **kwargs)
qset_filter &= Q(profile__is_cof=False)
return qset_filter
# Fetching data from User and CofProfile tables
queries["members"] = CofProfile.objects.filter(is_cof=True)
queries["users"] = User.objects.filter(profile__is_cof=False)
for bit in bits:
queries["members"] = queries["members"].filter(
Q(user__first_name__icontains=bit)
| Q(user__last_name__icontains=bit)
| Q(user__username__icontains=bit)
| Q(login_clipper__icontains=bit)
)
queries["users"] = queries["users"].filter(
Q(first_name__icontains=bit)
| Q(last_name__icontains=bit)
| Q(username__icontains=bit)
)
queries["members"] = queries["members"].distinct()
queries["users"] = queries["users"].distinct()
# Clearing redundancies
usernames = set(queries["members"].values_list("login_clipper", flat="True")) | set(
queries["users"].values_list("profile__login_clipper", flat="True")
)
class COFSearch(autocomplete.Compose):
search_units = [
("members", "username", COFMemberSearch),
("others", "username", COFOthersSearch),
("clippers", "clipper", autocomplete.LDAPSearch),
]
# Fetching data from the SPI
if getattr(settings, "LDAP_SERVER_URL", None):
# Fetching
ldap_query = "(&{:s})".format(
"".join(
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=bit)
for bit in bits
if bit.isalnum()
)
)
if ldap_query != "(&)":
# If none of the bits were legal, we do not perform the query
entries = None
with Connection(settings.LDAP_SERVER_URL) as conn:
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
entries = conn.entries
# Clearing redundancies
queries["clippers"] = [
Clipper(entry.uid.value, entry.cn.value)
for entry in entries
if entry.uid.value and entry.uid.value not in usernames
]
# Resulting data
data.update(queries)
data["options"] = sum(len(query) for query in queries)
cof_search = COFSearch()
return shortcuts.render(request, "autocomplete_user.html", data)
class AutocompleteView(TemplateView):
template_name = "gestioncof/search_results.html"
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(*args, **kwargs)
if "q" not in self.request.GET:
raise Http404
q = self.request.GET["q"]
ctx["q"] = q
ctx.update(cof_search.search(q.split()))
return ctx
autocomplete = buro_required(AutocompleteView.as_view())

View file

@ -1,29 +0,0 @@
{% load utils %}
<ul>
{% if members %}
<li class="autocomplete-header">Membres du COF</li>
{% for member in members %}{% if forloop.counter < 5 %}
<li class="autocomplete-value"><a href="{% url 'user-registration' member.user.username %}">{{ member.user|highlight_user:q }}</a></li>
{% elif forloop.counter == 5 %}<li class="autocomplete-more">...</a>{% endif %}{% endfor %}
{% endif %}
{% if users %}
<li class="autocomplete-header">Utilisateurs de GestioCOF</li>
{% for user in users %}{% if forloop.counter < 5 %}
<li class="autocomplete-value"><a href="{% url 'user-registration' user.username %}">{{ user|highlight_user:q }}</a></li>
{% elif forloop.counter == 5 %}<li class="autocomplete-more">...</a>{% endif %}{% endfor %}
{% endif %}
{% if clippers %}
<li class="autocomplete-header">Utilisateurs <tt>clipper</tt></li>
{% for clipper in clippers %}{% if forloop.counter < 5 %}
<li class="autocomplete-value"><a href="{% url 'clipper-registration' clipper.clipper clipper.fullname %}">{{ clipper|highlight_clipper:q }}</a></li>
{% elif forloop.counter == 5 %}<li class="autocomplete-more">...</a>{% endif %}{% endfor %}
{% endif %}
{% if not options %}
<li class="autocomplete-header">Aucune correspondance trouvée</li>
{% else %}
<li class="autocomplete-header">Pas dans la liste ?</li>
{% endif %}
<li><a href="{% url 'empty-registration' %}">Créer un compte</a></li>
</ul>

View file

@ -0,0 +1,56 @@
{% load utils %}
<ul>
{% if members %}
<li class="autocomplete-header">Membres</li>
{% for user in members %}
{% if forloop.counter < 5 %}
<li class="autocomplete-value">
<a href="{% url "user-registration" user.username %}">
{{ user|highlight_user:q }}
</a>
</li>
{% elif forloop.counter == 5 %}
<li class="autocomplete-more">...</li>
{% endif %}
{% endfor %}
{% endif %}
{% if others %}
<li class="autocomplete-header">Non-membres</li>
{% for user in others %}
{% if forloop.counter < 5 %}
<li class="autocomplete-value">
<a href="{% url "user-registration" user.username %}">
{{ user|highlight_user:q }}
</a>
</li>
{% elif forloop.counter == 5 %}
<li class="autocomplete-more">...</li>
{% endif %}
{% endfor %}
{% endif %}
{% if clippers %}
<li class="autocomplete-header">Utilisateurs <tt>clipper</tt></li>
{% for clipper in clippers %}
{% if forloop.counter < 5 %}
<li class="autocomplete-value">
<a href="{% url "clipper-registration" clipper.clipper clipper.fullname %}">
{{ clipper|highlight_clipper:q }}
</a>
</li>
{% elif forloop.counter == 5 %}
<li class="autocomplete-more">...</li>
{% endif %}
{% endfor %}
{% endif %}
{% if total %}
<li class="autocomplete-header">Pas dans la liste ?</li>
{% else %}
<li class="autocomplete-header">Aucune correspondance trouvée</li>
{% endif %}
<li><a href="{% url "empty-registration" %}">Créer un compte</a></li>
</ul>

View file

@ -15,9 +15,9 @@ from django.test import Client, TestCase, override_settings
from django.urls import reverse
from bda.models import Salle, Tirage
from gestioncof.autocomplete import Clipper
from gestioncof.models import CalendarSubscription, Club, Event, Survey, SurveyAnswer
from gestioncof.tests.testcases import ViewTestCaseMixin
from shared.views.autocomplete import Clipper
from .utils import create_member, create_root, create_user
@ -285,21 +285,19 @@ class RegistrationAutocompleteViewTests(ViewTestCaseMixin, TestCase):
self.mockLDAP([])
def _test(self, query, expected_users, expected_members, expected_clippers):
def _test(self, query, expected_others, expected_members, expected_clippers):
r = self.client.get(self.url, {"q": query})
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
r.context["users"], map(repr, expected_users), ordered=False
r.context["others"], map(repr, expected_others), ordered=False
)
self.assertQuerysetEqual(
r.context["members"],
map(lambda u: repr(u.profile), expected_members),
ordered=False,
r.context["members"], map(repr, expected_members), ordered=False,
)
self.assertCountEqual(
map(str, r.context.get("clippers", [])), map(str, expected_clippers)
map(str, r.context["clippers"]), map(str, expected_clippers)
)
def test_username(self):
@ -322,7 +320,7 @@ class RegistrationAutocompleteViewTests(ViewTestCaseMixin, TestCase):
mock_ldap.search.assert_called_once_with(
"dc=spi,dc=ens,dc=fr",
"(&(|(cn=*aa*)(uid=*aa*))(|(cn=*bb*)(uid=*bb*)))",
attributes=["uid", "cn"],
attributes=["cn", "uid"],
)
def test_clipper_escaped(self):
@ -333,14 +331,14 @@ class RegistrationAutocompleteViewTests(ViewTestCaseMixin, TestCase):
mock_ldap.search.assert_not_called()
def test_clipper_no_duplicate(self):
self.mockLDAP([("uid", "uu_u1")])
self.mockLDAP([("uid", "abc")])
self._test("uu u1", [self.u1], [], [Clipper("uid", "uu_u1")])
self._test("abc", [self.u1], [], [Clipper("uid", "abc")])
self.u1.profile.login_clipper = "uid"
self.u1.profile.save()
self.u1.username = "uid"
self.u1.save()
self._test("uu u1", [self.u1], [], [])
self._test("abc", [self.u1], [], [])
class HomeViewTests(ViewTestCaseMixin, TestCase):

View file

@ -58,7 +58,7 @@ from gestioncof.models import (
SurveyQuestion,
SurveyQuestionAnswer,
)
from utils.views.autocomplete import Select2QuerySetView
from shared.views.autocomplete import Select2QuerySetView
class HomeView(LoginRequiredMixin, TemplateView):