diff --git a/bds/autocomplete.py b/bds/autocomplete.py
new file mode 100644
index 00000000..0a240cea
--- /dev/null
+++ b/bds/autocomplete.py
@@ -0,0 +1,37 @@
+from django.contrib.auth import get_user_model
+from django.db.models import Q
+
+from shared.views import autocomplete
+
+User = get_user_model()
+
+
+class BDSMemberSearch(autocomplete.ModelSearch):
+ model = User
+ search_fields = ["username", "first_name", "last_name"]
+
+ def get_queryset_filter(self, *args, **kwargs):
+ qset_filter = super().get_queryset_filter(*args, **kwargs)
+ qset_filter &= Q(bds__is_member=True)
+ return qset_filter
+
+
+class BDSOthersSearch(autocomplete.ModelSearch):
+ model = User
+ search_fields = ["username", "first_name", "last_name"]
+
+ def get_queryset_filter(self, *args, **kwargs):
+ qset_filter = super().get_queryset_filter(*args, **kwargs)
+ qset_filter &= Q(bds__isnull=True) | Q(bds__is_member=False)
+ return qset_filter
+
+
+class BDSSearch(autocomplete.Compose):
+ search_units = [
+ ("members", "username", BDSMemberSearch),
+ ("others", "username", BDSOthersSearch),
+ ("clippers", "clipper", autocomplete.LDAPSearch),
+ ]
+
+
+bds_search = BDSSearch()
diff --git a/bds/mixins.py b/bds/mixins.py
new file mode 100644
index 00000000..14fac693
--- /dev/null
+++ b/bds/mixins.py
@@ -0,0 +1,5 @@
+from django.contrib.auth.mixins import PermissionRequiredMixin
+
+
+class StaffRequiredMixin(PermissionRequiredMixin):
+ permission_required = "bds:is_team"
diff --git a/bds/static/bds/css/bds.css b/bds/static/bds/css/bds.css
new file mode 100644
index 00000000..fe9b2fa2
--- /dev/null
+++ b/bds/static/bds/css/bds.css
@@ -0,0 +1,82 @@
+html, body {
+ padding: 0;
+ margin: 0;
+ background: #ddcecc;
+ font-size: 18px;
+}
+
+a {
+ text-decoration: none;
+ color: #a82305;
+}
+
+/* header */
+
+nav {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: space-between;
+ align-items: center;
+ background: #3e2263;
+ height: 3em;
+ padding: 0.4em 1em;
+}
+
+nav a, nav a img {
+ height: 100%;
+}
+
+input[type="text"] {
+ font-size: 18px;
+}
+
+#search_autocomplete {
+ flex: 1;
+ width: 480px;
+ margin: 0;
+ border: 0;
+ padding: 10px 10px;
+}
+
+.highlight {
+ text-decoration: underline;
+ font-weight: bold;
+}
+
+.yourlabs-autocomplete ul {
+ width: 500px;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.yourlabs-autocomplete ul li {
+ height: 2em;
+ line-height: 2em;
+ width: 500px;
+ padding: 0;
+}
+
+.yourlabs-autocomplete ul li.hilight {
+ background: #e8554e;
+}
+
+.yourlabs-autocomplete ul li a {
+ color: inherit;
+}
+
+.autocomplete-item {
+ display: block;
+ width: 480px;
+ height: 100%;
+ padding: 2px 10px;
+ margin: 0;
+}
+
+.autocomplete-header {
+ background: #b497e1;
+}
+
+.autocomplete-value, .autocomplete-new, .autocomplete-more {
+ background: white;
+}
diff --git a/bds/static/bds/images/logo.svg b/bds/static/bds/images/logo.svg
new file mode 100644
index 00000000..15292488
--- /dev/null
+++ b/bds/static/bds/images/logo.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bds/static/bds/images/logout.svg b/bds/static/bds/images/logout.svg
new file mode 100644
index 00000000..12489bbd
--- /dev/null
+++ b/bds/static/bds/images/logout.svg
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+image/svg+xml Openclipart
diff --git a/bds/templates/bds/base.html b/bds/templates/bds/base.html
new file mode 100644
index 00000000..0bf34287
--- /dev/null
+++ b/bds/templates/bds/base.html
@@ -0,0 +1,23 @@
+{% load staticfiles %}
+
+
+
+
+ {{ site.name }}
+
+
+
+
+ {# CSS #}
+
+
+ {# Javascript #}
+
+
+
+
+ {% include "bds/nav.html" %}
+
+ {% block content %}{% endblock %}
+
+
diff --git a/bds/templates/bds/home.html b/bds/templates/bds/home.html
new file mode 100644
index 00000000..1ae76227
--- /dev/null
+++ b/bds/templates/bds/home.html
@@ -0,0 +1,22 @@
+{% extends "bds/base.html" %}
+
+{% block content %}
+
+ Bienvenue sur GestioBDS !
+
+
+
+
+ Le site est encore en développement.
+
+ Suivez notre avancement sur
+
+ cette milestone sur le gitlab de l'ENS.
+
+ Faites vos remarques par mail à
+
klub-dev@ens.fr
+ ou en ouvrant une
+
+ issue .
+
+{% endblock %}
diff --git a/bds/templates/bds/nav.html b/bds/templates/bds/nav.html
new file mode 100644
index 00000000..e1118caa
--- /dev/null
+++ b/bds/templates/bds/nav.html
@@ -0,0 +1,40 @@
+{% load i18n %}
+{% load static %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bds/templates/bds/search_results.html b/bds/templates/bds/search_results.html
new file mode 100644
index 00000000..b8d5e241
--- /dev/null
+++ b/bds/templates/bds/search_results.html
@@ -0,0 +1,75 @@
+{% load i18n %}
+{% load search_utils %}
+
+
+ {% if members %}
+
+ {% for user in members %}
+ {% if forloop.counter < 5 %}
+
+
+ {{ user|highlight_user:q }}
+
+
+ {% elif forloop.counter == 5 %}
+
+ ...
+
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+
+ {% if others %}
+
+ {% for user in others %}
+ {% if forloop.counter < 5 %}
+
+
+ {{ user|highlight_user:q }}
+
+
+ {% elif forloop.counter == 5 %}
+
+ ...
+
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+
+ {% if clippers %}
+
+ {% for clipper in clippers %}
+ {% if forloop.counter < 5 %}
+
+
+ {{ clipper|highlight_clipper:q }}
+
+
+ {% elif forloop.counter == 5 %}
+
+ ...
+
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+
+ {% if total %}
+
+ {% else %}
+
+ {% endif %}
+
+
+ {% trans "Créer un compte" %}
+
+
diff --git a/bds/urls.py b/bds/urls.py
index e4487422..fbddccc6 100644
--- a/bds/urls.py
+++ b/bds/urls.py
@@ -1,2 +1,9 @@
-app_label = "bds"
-urlpatterns = []
+from django.urls import path
+
+from bds import views
+
+app_name = "bds"
+urlpatterns = [
+ path("", views.Home.as_view(), name="home"),
+ path("autocomplete", views.AutocompleteView.as_view(), name="autocomplete"),
+]
diff --git a/bds/views.py b/bds/views.py
index 60f00ef0..a2ba3a2c 100644
--- a/bds/views.py
+++ b/bds/views.py
@@ -1 +1,24 @@
-# Create your views here.
+from django.http import Http404
+from django.views.generic import TemplateView
+
+from bds.autocomplete import bds_search
+from bds.mixins import StaffRequiredMixin
+
+
+class AutocompleteView(StaffRequiredMixin, TemplateView):
+ template_name = "bds/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
+ results = bds_search.search(q.split())
+ ctx.update(results)
+ ctx["total"] = sum((len(r) for r in results.values()))
+ return ctx
+
+
+class Home(StaffRequiredMixin, TemplateView):
+ template_name = "bds/home.html"
diff --git a/gestioncof/templates/gestioncof/search_results.html b/gestioncof/templates/gestioncof/search_results.html
index ba8b6580..126649b6 100644
--- a/gestioncof/templates/gestioncof/search_results.html
+++ b/gestioncof/templates/gestioncof/search_results.html
@@ -1,4 +1,4 @@
-{% load utils %}
+{% load search_utils %}
{% if members %}
diff --git a/gestioncof/templatetags/utils.py b/gestioncof/templatetags/utils.py
index 21518614..6b2122b6 100644
--- a/gestioncof/templatetags/utils.py
+++ b/gestioncof/templatetags/utils.py
@@ -1,7 +1,4 @@
-import re
-
from django import template
-from django.utils.safestring import mark_safe
register = template.Library()
@@ -15,29 +12,3 @@ def key(d, key_name):
value = settings.TEMPLATE_STRING_IF_INVALID
return value
-
-
-def highlight_text(text, q):
- q2 = "|".join(re.escape(word) for word in q.split())
- pattern = re.compile(r"(?P%s)" % q2, re.IGNORECASE)
- return mark_safe(
- re.sub(pattern, r"\g ", text)
- )
-
-
-@register.filter
-def highlight_user(user, q):
- if user.first_name and user.last_name:
- text = "%s %s (%s )" % (user.first_name, user.last_name, user.username)
- else:
- text = user.username
- return highlight_text(text, q)
-
-
-@register.filter
-def highlight_clipper(clipper, q):
- if clipper.fullname:
- text = "%s (%s )" % (clipper.fullname, clipper.clipper)
- else:
- text = clipper.clipper
- return highlight_text(text, q)
diff --git a/gestioncof/templatetags/__init__.py b/shared/templatetags/__init__.py
similarity index 100%
rename from gestioncof/templatetags/__init__.py
rename to shared/templatetags/__init__.py
diff --git a/shared/templatetags/search_utils.py b/shared/templatetags/search_utils.py
new file mode 100644
index 00000000..28851248
--- /dev/null
+++ b/shared/templatetags/search_utils.py
@@ -0,0 +1,32 @@
+import re
+
+from django import template
+from django.utils.safestring import mark_safe
+
+register = template.Library()
+
+
+def highlight_text(text, q):
+ q2 = "|".join(re.escape(word) for word in q.split())
+ pattern = re.compile(r"(?P%s)" % q2, re.IGNORECASE)
+ return mark_safe(
+ re.sub(pattern, r"\g ", text)
+ )
+
+
+@register.filter
+def highlight_user(user, q):
+ if user.first_name and user.last_name:
+ text = "%s %s (%s )" % (user.first_name, user.last_name, user.username)
+ else:
+ text = user.username
+ return highlight_text(text, q)
+
+
+@register.filter
+def highlight_clipper(clipper, q):
+ if clipper.fullname:
+ text = "%s (%s )" % (clipper.fullname, clipper.clipper)
+ else:
+ text = clipper.clipper
+ return highlight_text(text, q)
diff --git a/shared/views/autocomplete.py b/shared/views/autocomplete.py
index 168abc4b..50d0d2c2 100644
--- a/shared/views/autocomplete.py
+++ b/shared/views/autocomplete.py
@@ -1,3 +1,4 @@
+import logging
from collections import namedtuple
from dal import autocomplete
@@ -12,6 +13,9 @@ else:
ldap = None
+django_logger = logging.getLogger("django.request")
+
+
class SearchUnit:
"""Base class for all the search utilities.
@@ -128,17 +132,21 @@ class LDAPSearch(SearchUnit):
if ldap is None or query == "(&)":
return []
- ldap_obj = ldap.initialize(self.ldap_server_url)
- res = ldap_obj.search_s(
- self.domain_component, ldap.SCOPE_SUBTREE, query, self.search_fields
- )
- return [
- Clipper(
- clipper=attrs["uid"][0].decode("utf-8"),
- fullname=attrs["cn"][0].decode("utf-8"),
+ try:
+ ldap_obj = ldap.initialize(self.ldap_server_url)
+ res = ldap_obj.search_s(
+ self.domain_component, ldap.SCOPE_SUBTREE, query, self.search_fields
)
- for (_, attrs) in res
- ]
+ return [
+ Clipper(
+ clipper=attrs["uid"][0].decode("utf-8"),
+ fullname=attrs["cn"][0].decode("utf-8"),
+ )
+ for (_, attrs) in res
+ ]
+ except ldap.LDAPError as err:
+ django_logger.error("An LDAP error occurred", exc_info=err)
+ return []
# ---