1
0
mirror of https://gitlab.com/animath/si/plateforme-corres2math.git synced 2025-02-06 08:53:00 +00:00

Merge branch 'indexing' into 'django'

Indexing

See merge request animath/si/plateforme-corres2math!1
This commit is contained in:
Yohann D'ANELLO 2020-10-15 19:10:35 +00:00
commit 07451d41d3
25 changed files with 390 additions and 88 deletions

View File

@ -1,11 +1,12 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_save, pre_save from django.db.models.signals import post_save, pre_delete, pre_save
class ParticipationConfig(AppConfig): class ParticipationConfig(AppConfig):
name = 'participation' name = 'participation'
def ready(self): def ready(self):
from participation.signals import create_team_participation, update_mailing_list from participation.signals import create_team_participation, delete_related_videos, update_mailing_list
pre_save.connect(update_mailing_list, "participation.Team") pre_save.connect(update_mailing_list, "participation.Team")
pre_delete.connect(delete_related_videos, "participation.Participation")
post_save.connect(create_team_participation, "participation.Team") post_save.connect(create_team_participation, "participation.Team")

View File

@ -5,6 +5,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db import models from django.db import models
from django.db.models import Index from django.db.models import Index
from django.urls import reverse_lazy
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.text import format_lazy from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -56,6 +57,9 @@ class Team(models.Model):
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse_lazy("participation:team_detail", args=(self.pk,))
def __str__(self): def __str__(self):
return _("Team {name} ({trigram})").format(name=self.name, trigram=self.trigram) return _("Team {name} ({trigram})").format(name=self.name, trigram=self.trigram)
@ -115,6 +119,9 @@ class Participation(models.Model):
verbose_name=_("synthesis video"), verbose_name=_("synthesis video"),
) )
def get_absolute_url(self):
return reverse_lazy("participation:participation_detail", args=(self.pk,))
def __str__(self): def __str__(self):
return _("Participation of the team {name} ({trigram})").format(name=self.team.name, trigram=self.team.trigram) return _("Participation of the team {name} ({trigram})").format(name=self.team.name, trigram=self.team.trigram)

View File

@ -0,0 +1,24 @@
from haystack import indexes
from .models import Participation, Team, Video
class TeamIndex(indexes.ModelSearchIndex, indexes.Indexable):
text = indexes.NgramField(document=True, use_template=True)
class Meta:
model = Team
class ParticipationIndex(indexes.ModelSearchIndex, indexes.Indexable):
text = indexes.NgramField(document=True, use_template=True)
class Meta:
model = Participation
class VideoIndex(indexes.ModelSearchIndex, indexes.Indexable):
text = indexes.NgramField(document=True, use_template=True)
class Meta:
model = Video

View File

@ -23,3 +23,10 @@ def update_mailing_list(instance: Team, **_):
for coach in instance.coachs.all(): for coach in instance.coachs.all():
get_sympa_client().subscribe(coach.user.email, f"equipe-{instance.trigram.lower()}", False, get_sympa_client().subscribe(coach.user.email, f"equipe-{instance.trigram.lower()}", False,
f"{coach.user.first_name} {coach.user.last_name}") f"{coach.user.first_name} {coach.user.last_name}")
def delete_related_videos(instance: Participation, **_):
if instance.solution:
instance.solution.delete()
if instance.synthesis:
instance.synthesis.delete()

View File

@ -0,0 +1,70 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from .models import Team
# noinspection PyTypeChecker
class TeamTable(tables.Table):
name = tables.LinkColumn(
'participation:team_detail',
args=[tables.A("id")],
verbose_name=lambda: _("name").capitalize(),
)
problem = tables.Column(
accessor="participation__problem",
verbose_name=lambda: _("problem number").capitalize(),
)
class Meta:
attrs = {
'class': 'table table condensed table-striped',
}
model = Team
fields = ('name', 'trigram', 'problem',)
template_name = 'django_tables2/bootstrap4.html'
# noinspection PyTypeChecker
class ParticipationTable(tables.Table):
name = tables.LinkColumn(
'participation:participation_detail',
args=[tables.A("id")],
verbose_name=lambda: _("name").capitalize(),
accessor="team__name",
)
trigram = tables.Column(
verbose_name=lambda: _("trigram").capitalize(),
accessor="team__trigram",
)
problem = tables.Column(
verbose_name=lambda: _("problem number").capitalize(),
)
class Meta:
attrs = {
'class': 'table table condensed table-striped',
}
model = Team
fields = ('name', 'trigram', 'problem',)
template_name = 'django_tables2/bootstrap4.html'
class VideoTable(tables.Table):
participation_name = tables.LinkColumn(
'participation:participation_detail',
args=[tables.A("participation__pk")],
verbose_name=lambda: _("name").capitalize(),
accessor="participation__name",
)
class Meta:
attrs = {
'class': 'table table condensed table-striped',
}
model = Team
fields = ('participation_name', 'link',)
template_name = 'django_tables2/bootstrap4.html'

View File

@ -0,0 +1,2 @@
{{ object.team.name }}
{{ object.team.trigram }}

View File

@ -0,0 +1,2 @@
{{ object.name }}
{{ object.trigram }}

View File

@ -0,0 +1 @@
{{ object.link }}

View File

@ -1,12 +1,11 @@
import os
from io import BytesIO from io import BytesIO
import os
from zipfile import ZipFile from zipfile import ZipFile
from django.core.mail import send_mail
from corres2math.lists import get_sympa_client from corres2math.lists import get_sympa_client
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail
from django.db import transaction from django.db import transaction
from django.http import HttpResponse from django.http import HttpResponse
from django.template.loader import render_to_string from django.template.loader import render_to_string

View File

@ -2,6 +2,7 @@ from corres2math.tokens import email_validation_token
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.db import models from django.db import models
from django.template import loader from django.template import loader
from django.urls import reverse_lazy
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode from django.utils.http import urlsafe_base64_encode
@ -63,6 +64,9 @@ class Registration(PolymorphicModel):
def is_admin(self): def is_admin(self):
return isinstance(self, AdminRegistration) or self.user.is_superuser return isinstance(self, AdminRegistration) or self.user.is_superuser
def get_absolute_url(self):
return reverse_lazy("registration:user_detail", args=(self.user_id,))
def __str__(self): def __str__(self):
return f"{self.user.first_name} {self.user.last_name}" return f"{self.user.first_name} {self.user.last_name}"

View File

@ -0,0 +1,10 @@
from haystack import indexes
from .models import Registration
class RegistrationIndex(indexes.ModelSearchIndex, indexes.Indexable):
text = indexes.NgramField(document=True, use_template=True)
class Meta:
model = Registration

View File

@ -0,0 +1,21 @@
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from .models import Registration
class RegistrationTable(tables.Table):
last_name = tables.LinkColumn(
'registration:user_detail',
args=[tables.A("user_id")],
verbose_name=lambda: _("last name").capitalize(),
accessor="user__last_name",
)
class Meta:
attrs = {
'class': 'table table condensed table-striped',
}
model = Registration
fields = ('last_name', 'user__first_name', 'user__email', 'type',)
template_name = 'django_tables2/bootstrap4.html'

View File

@ -0,0 +1,5 @@
{{ object.user.last_name }}
{{ object.user.first_name }}
{{ object.user.email }}
{{ object.type }}
{{ object.role }}

View File

@ -0,0 +1,7 @@
{{ object.user.first_name }}
{{ object.user.last_name }}
{{ object.user.email }}
{{ object.type }}
{{ object.professional_activity }}
{{ object.team.name }}
{{ object.team.trigram }}

View File

@ -0,0 +1,8 @@
{{ object.user.first_name }}
{{ object.user.last_name }}
{{ object.user.email }}
{{ object.type }}
{{ object.get_student_class_display }}
{{ object.school }}
{{ object.team.name }}
{{ object.team.trigram }}

View File

@ -0,0 +1,26 @@
from django import template
from django_tables2 import Table
from participation.models import Participation, Team, Video
from participation.tables import ParticipationTable, TeamTable, VideoTable
from ..models import Registration
from ..tables import RegistrationTable
def search_table(results):
model_class = results[0].object.__class__
if issubclass(model_class, Registration):
table_class = RegistrationTable
elif issubclass(model_class, Team):
table_class = TeamTable
elif issubclass(model_class, Participation):
table_class = ParticipationTable
elif issubclass(model_class, Video):
table_class = VideoTable
else:
table_class = Table
return table_class([result.object for result in results], prefix=model_class._meta.model_name)
register = template.Library()
register.filter("search_table", search_table)

View File

@ -54,6 +54,7 @@ INSTALLED_APPS = [
'crispy_forms', 'crispy_forms',
'django_extensions', 'django_extensions',
'django_tables2', 'django_tables2',
'haystack',
'logs', 'logs',
'mailer', 'mailer',
'polymorphic', 'polymorphic',
@ -181,6 +182,15 @@ CRISPY_TEMPLATE_PACK = 'bootstrap4'
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html' DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap4.html'
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
}
}
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
_db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower() _db_type = os.getenv('DJANGO_DB_TYPE', 'sqlite').lower()
if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql': if _db_type == 'mysql' or _db_type.startswith('postgres') or _db_type == 'psql':

View File

@ -13,19 +13,21 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.views.defaults import bad_request, permission_denied, page_not_found, server_error from django.views.defaults import bad_request, permission_denied, page_not_found, server_error
from django.views.generic import TemplateView from django.views.generic import TemplateView
from registration.views import PhotoAuthorizationView from registration.views import PhotoAuthorizationView
from .views import AdminSearchView
urlpatterns = [ urlpatterns = [
path('', TemplateView.as_view(template_name="index.html"), name='index'), path('', TemplateView.as_view(template_name="index.html"), name='index'),
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),
path('admin/doc/', include('django.contrib.admindocs.urls')), path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls, name="admin"), path('admin/', admin.site.urls, name="admin"),
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('search/', AdminSearchView.as_view(), name="haystack_search"),
path('api/', include('api.urls')), path('api/', include('api.urls')),
path('participation/', include('participation.urls')), path('participation/', include('participation.urls')),

13
corres2math/views.py Normal file
View File

@ -0,0 +1,13 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.utils.translation import gettext_lazy as _
from haystack.generic_views import SearchView
class AdminSearchView(LoginRequiredMixin, SearchView):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if not request.user.registration.is_admin:
raise PermissionDenied(_("Only administrators are allowed to perform a full research."))
return super().dispatch(request, *args, **kwargs)

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Corres2math\n" "Project-Id-Version: Corres2math\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-11 18:43+0200\n" "POT-Creation-Date: 2020-10-15 20:47+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n" "Last-Translator: Yohann D'ANELLO <yohann.danello@animath.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -30,7 +30,7 @@ msgid "This task failed successfully."
msgstr "Cette tâche a échoué avec succès." msgstr "Cette tâche a échoué avec succès."
#: apps/eastereggs/templates/eastereggs/xp_modal.html:16 #: apps/eastereggs/templates/eastereggs/xp_modal.html:16
#: templates/base_modal.html:16 #: templates/base_modal.html:18
msgid "Close" msgid "Close"
msgstr "Fermer" msgstr "Fermer"
@ -38,7 +38,7 @@ msgstr "Fermer"
msgid "Logs" msgid "Logs"
msgstr "Logs" msgstr "Logs"
#: apps/logs/models.py:22 apps/registration/models.py:16 #: apps/logs/models.py:22 apps/registration/models.py:17
msgid "user" msgid "user"
msgstr "utilisateur" msgstr "utilisateur"
@ -99,7 +99,7 @@ msgstr "changelogs"
msgid "Changelog of type \"{action}\" for model {model} at {timestamp}" msgid "Changelog of type \"{action}\" for model {model} at {timestamp}"
msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}" msgstr "Changelog de type \"{action}\" pour le modèle {model} le {timestamp}"
#: apps/participation/forms.py:14 apps/participation/models.py:23 #: apps/participation/forms.py:14 apps/participation/models.py:24
msgid "The trigram must be composed of three uppercase letters." msgid "The trigram must be composed of three uppercase letters."
msgstr "Le trigramme doit être composé de trois lettres majuscules." msgstr "Le trigramme doit être composé de trois lettres majuscules."
@ -115,27 +115,28 @@ msgstr "Je m'engage à participer à l'intégralité des Correspondances."
msgid "Message to address to the team:" msgid "Message to address to the team:"
msgstr "Message à adresser à l'équipe :" msgstr "Message à adresser à l'équipe :"
#: apps/participation/models.py:16 #: apps/participation/models.py:17 apps/participation/tables.py:12
#: apps/participation/tables.py:34 apps/participation/tables.py:60
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
#: apps/participation/models.py:22 #: apps/participation/models.py:23 apps/participation/tables.py:39
msgid "trigram" msgid "trigram"
msgstr "trigramme" msgstr "trigramme"
#: apps/participation/models.py:30 #: apps/participation/models.py:31
msgid "access code" msgid "access code"
msgstr "code d'accès" msgstr "code d'accès"
#: apps/participation/models.py:31 #: apps/participation/models.py:32
msgid "The access code let other people to join the team." msgid "The access code let other people to join the team."
msgstr "Le code d'accès permet aux autres participants de rejoindre l'équipe." msgstr "Le code d'accès permet aux autres participants de rejoindre l'équipe."
#: apps/participation/models.py:35 #: apps/participation/models.py:36
msgid "Grant Animath to publish my video" msgid "Grant Animath to publish my video"
msgstr "Autoriser Animath à publier ma vidéo" msgstr "Autoriser Animath à publier ma vidéo"
#: apps/participation/models.py:36 #: apps/participation/models.py:37
msgid "" msgid ""
"Give the authorisation to publish the video on the main website to promote " "Give the authorisation to publish the video on the main website to promote "
"the action." "the action."
@ -143,90 +144,91 @@ msgstr ""
"Donner l'autorisation de publier la vidéo sur le site principal pour " "Donner l'autorisation de publier la vidéo sur le site principal pour "
"promouvoir les Correspondances." "promouvoir les Correspondances."
#: apps/participation/models.py:60 #: apps/participation/models.py:64
#, python-brace-format #, python-brace-format
msgid "Team {name} ({trigram})" msgid "Team {name} ({trigram})"
msgstr "Équipe {name} ({trigram})" msgstr "Équipe {name} ({trigram})"
#: apps/participation/models.py:63 apps/participation/models.py:74 #: apps/participation/models.py:67 apps/participation/models.py:78
#: apps/registration/models.py:85 apps/registration/models.py:130 #: apps/registration/models.py:89 apps/registration/models.py:134
msgid "team" msgid "team"
msgstr "équipe" msgstr "équipe"
#: apps/participation/models.py:64 #: apps/participation/models.py:68
msgid "teams" msgid "teams"
msgstr "équipes" msgstr "équipes"
#: apps/participation/models.py:78 #: apps/participation/models.py:82
#, python-brace-format #, python-brace-format
msgid "Problem #{problem:d}" msgid "Problem #{problem:d}"
msgstr "Problème n°{problem:d}" msgstr "Problème n°{problem:d}"
#: apps/participation/models.py:81 #: apps/participation/models.py:85 apps/participation/tables.py:17
#: apps/participation/tables.py:44
msgid "problem number" msgid "problem number"
msgstr "numéro de problème" msgstr "numéro de problème"
#: apps/participation/models.py:87 apps/participation/models.py:135 #: apps/participation/models.py:91 apps/participation/models.py:142
msgid "valid" msgid "valid"
msgstr "valide" msgstr "valide"
#: apps/participation/models.py:88 apps/participation/models.py:136 #: apps/participation/models.py:92 apps/participation/models.py:143
msgid "The video got the validation of the administrators." msgid "The video got the validation of the administrators."
msgstr "La vidéo a été validée par les administrateurs." msgstr "La vidéo a été validée par les administrateurs."
#: apps/participation/models.py:97 #: apps/participation/models.py:101
msgid "solution video" msgid "solution video"
msgstr "vidéo de solution" msgstr "vidéo de solution"
#: apps/participation/models.py:106 #: apps/participation/models.py:110
msgid "received participation" msgid "received participation"
msgstr "participation reçue" msgstr "participation reçue"
#: apps/participation/models.py:115 #: apps/participation/models.py:119
msgid "synthesis video" msgid "synthesis video"
msgstr "vidéo de synthèse" msgstr "vidéo de synthèse"
#: apps/participation/models.py:119 #: apps/participation/models.py:126
#, python-brace-format #, python-brace-format
msgid "Participation of the team {name} ({trigram})" msgid "Participation of the team {name} ({trigram})"
msgstr "Participation de l'équipe {name} ({trigram})" msgstr "Participation de l'équipe {name} ({trigram})"
#: apps/participation/models.py:122 #: apps/participation/models.py:129
msgid "participation" msgid "participation"
msgstr "participation" msgstr "participation"
#: apps/participation/models.py:123 #: apps/participation/models.py:130
msgid "participations" msgid "participations"
msgstr "participations" msgstr "participations"
#: apps/participation/models.py:128 #: apps/participation/models.py:135
msgid "link" msgid "link"
msgstr "lien" msgstr "lien"
#: apps/participation/models.py:129 #: apps/participation/models.py:136
msgid "The full video link." msgid "The full video link."
msgstr "Le lien complet de la vidéo." msgstr "Le lien complet de la vidéo."
#: apps/participation/models.py:158 #: apps/participation/models.py:165
#, python-brace-format #, python-brace-format
msgid "Video of team {name} ({trigram})" msgid "Video of team {name} ({trigram})"
msgstr "Vidéo de l'équipe {name} ({trigram})" msgstr "Vidéo de l'équipe {name} ({trigram})"
#: apps/participation/models.py:162 #: apps/participation/models.py:169
msgid "video" msgid "video"
msgstr "vidéo" msgstr "vidéo"
#: apps/participation/models.py:163 #: apps/participation/models.py:170
msgid "videos" msgid "videos"
msgstr "vidéos" msgstr "vidéos"
#: apps/participation/templates/participation/create_team.html:11 #: apps/participation/templates/participation/create_team.html:11
#: templates/base.html:207 #: templates/base.html:220
msgid "Create" msgid "Create"
msgstr "Créer" msgstr "Créer"
#: apps/participation/templates/participation/join_team.html:11 #: apps/participation/templates/participation/join_team.html:11
#: templates/base.html:203 #: templates/base.html:216
msgid "Join" msgid "Join"
msgstr "Rejoindre" msgstr "Rejoindre"
@ -370,65 +372,65 @@ msgstr "Invalider"
msgid "Update team" msgid "Update team"
msgstr "Modifier l'équipe" msgstr "Modifier l'équipe"
#: apps/participation/views.py:25 templates/base.html:70 #: apps/participation/views.py:27 templates/base.html:70
#: templates/base.html:206 #: templates/base.html:219
msgid "Create team" msgid "Create team"
msgstr "Créer une équipe" msgstr "Créer une équipe"
#: apps/participation/views.py:32 apps/participation/views.py:62 #: apps/participation/views.py:34 apps/participation/views.py:64
msgid "You don't participate, so you can't create a team." msgid "You don't participate, so you can't create a team."
msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe." msgstr "Vous ne participez pas, vous ne pouvez pas créer d'équipe."
#: apps/participation/views.py:34 apps/participation/views.py:64 #: apps/participation/views.py:36 apps/participation/views.py:66
msgid "You are already in a team." msgid "You are already in a team."
msgstr "Vous êtes déjà dans une équipe." msgstr "Vous êtes déjà dans une équipe."
#: apps/participation/views.py:55 templates/base.html:75 #: apps/participation/views.py:57 templates/base.html:75
#: templates/base.html:202 #: templates/base.html:215
msgid "Join team" msgid "Join team"
msgstr "Rejoindre une équipe" msgstr "Rejoindre une équipe"
#: apps/participation/views.py:90 apps/participation/views.py:232 #: apps/participation/views.py:92 apps/participation/views.py:246
msgid "You are not in a team." msgid "You are not in a team."
msgstr "Vous n'êtes pas dans une équipe." msgstr "Vous n'êtes pas dans une équipe."
#: apps/participation/views.py:91 apps/participation/views.py:233 #: apps/participation/views.py:93 apps/participation/views.py:247
msgid "You don't participate, so you don't have any team." msgid "You don't participate, so you don't have any team."
msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe." msgstr "Vous ne participez pas, vous n'avez donc pas d'équipe."
#: apps/participation/views.py:130 #: apps/participation/views.py:132
msgid "You don't participate, so you can't request the validation of the team." msgid "You don't participate, so you can't request the validation of the team."
msgstr "" msgstr ""
"Vous ne participez pas, vous ne pouvez pas demander la validation de " "Vous ne participez pas, vous ne pouvez pas demander la validation de "
"l'équipe." "l'équipe."
#: apps/participation/views.py:133 #: apps/participation/views.py:135
msgid "The validation of the team is already done or pending." msgid "The validation of the team is already done or pending."
msgstr "La validation de l'équipe est déjà faite ou en cours." msgstr "La validation de l'équipe est déjà faite ou en cours."
#: apps/participation/views.py:146 #: apps/participation/views.py:148
msgid "You are not an administrator." msgid "You are not an administrator."
msgstr "Vous n'êtes pas administrateur." msgstr "Vous n'êtes pas administrateur."
#: apps/participation/views.py:149 #: apps/participation/views.py:151
msgid "This team has no pending validation." msgid "This team has no pending validation."
msgstr "L'équipe n'a pas de validation en attente." msgstr "L'équipe n'a pas de validation en attente."
#: apps/participation/views.py:159 #: apps/participation/views.py:173
msgid "You must specify if you validate the registration or not." msgid "You must specify if you validate the registration or not."
msgstr "Vous devez spécifier si vous validez l'inscription ou non." msgstr "Vous devez spécifier si vous validez l'inscription ou non."
#: apps/participation/views.py:216 apps/registration/views.py:213 #: apps/participation/views.py:230 apps/registration/views.py:213
#, python-brace-format #, python-brace-format
msgid "Photo authorization of {student}.{ext}" msgid "Photo authorization of {student}.{ext}"
msgstr "Autorisation de droit à l'image de {student}.{ext}" msgstr "Autorisation de droit à l'image de {student}.{ext}"
#: apps/participation/views.py:220 #: apps/participation/views.py:234
#, python-brace-format #, python-brace-format
msgid "Photo authorizations of team {trigram}.zip" msgid "Photo authorizations of team {trigram}.zip"
msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip" msgstr "Autorisations de droit à l'image de l'équipe {trigram}.zip"
#: apps/participation/views.py:242 #: apps/participation/views.py:256
msgid "The team is not validated yet." msgid "The team is not validated yet."
msgstr "L'équipe n'est pas encore validée." msgstr "L'équipe n'est pas encore validée."
@ -440,7 +442,7 @@ msgstr "rôle"
msgid "participant" msgid "participant"
msgstr "participant" msgstr "participant"
#: apps/registration/forms.py:16 apps/registration/models.py:139 #: apps/registration/forms.py:16 apps/registration/models.py:143
msgid "coach" msgid "coach"
msgstr "encadrant" msgstr "encadrant"
@ -448,91 +450,97 @@ msgstr "encadrant"
msgid "The uploaded file must be a PDF, PNG of JPEG file." msgid "The uploaded file must be a PDF, PNG of JPEG file."
msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG." msgstr "Le fichier envoyé doit être au format PDF, PNG ou JPEG."
#: apps/registration/models.py:21 #: apps/registration/models.py:22
msgid "Grant Animath to contact me in the future about other actions" msgid "Grant Animath to contact me in the future about other actions"
msgstr "" msgstr ""
"Autoriser Animath à me recontacter à l'avenir à propos d'autres actions" "Autoriser Animath à me recontacter à l'avenir à propos d'autres actions"
#: apps/registration/models.py:26 #: apps/registration/models.py:27
msgid "email confirmed" msgid "email confirmed"
msgstr "email confirmé" msgstr "email confirmé"
#: apps/registration/models.py:30 #: apps/registration/models.py:31
msgid "Activate your Correspondances account" msgid "Activate your Correspondances account"
msgstr "Activez votre compte des Correspondances" msgstr "Activez votre compte des Correspondances"
#: apps/registration/models.py:70 #: apps/registration/models.py:74
msgid "registration" msgid "registration"
msgstr "inscription" msgstr "inscription"
#: apps/registration/models.py:71 #: apps/registration/models.py:75
msgid "registrations" msgid "registrations"
msgstr "inscriptions" msgstr "inscriptions"
#: apps/registration/models.py:90 #: apps/registration/models.py:94
msgid "12th grade" msgid "12th grade"
msgstr "Terminale" msgstr "Terminale"
#: apps/registration/models.py:91 #: apps/registration/models.py:95
msgid "11th grade" msgid "11th grade"
msgstr "Première" msgstr "Première"
#: apps/registration/models.py:92 #: apps/registration/models.py:96
msgid "10th grade or lower" msgid "10th grade or lower"
msgstr "Seconde ou inférieur" msgstr "Seconde ou inférieur"
#: apps/registration/models.py:94 #: apps/registration/models.py:98
msgid "student class" msgid "student class"
msgstr "classe" msgstr "classe"
#: apps/registration/models.py:99 #: apps/registration/models.py:103
msgid "school" msgid "school"
msgstr "école" msgstr "école"
#: apps/registration/models.py:103 #: apps/registration/models.py:107
msgid "photo authorization" msgid "photo authorization"
msgstr "autorisation de droit à l'image" msgstr "autorisation de droit à l'image"
#: apps/registration/models.py:111 #: apps/registration/models.py:115
msgid "student" msgid "student"
msgstr "étudiant" msgstr "étudiant"
#: apps/registration/models.py:119 #: apps/registration/models.py:123
msgid "student registration" msgid "student registration"
msgstr "inscription d'élève" msgstr "inscription d'élève"
#: apps/registration/models.py:120 #: apps/registration/models.py:124
msgid "student registrations" msgid "student registrations"
msgstr "inscriptions d'élève" msgstr "inscriptions d'élève"
#: apps/registration/models.py:134 #: apps/registration/models.py:138
msgid "professional activity" msgid "professional activity"
msgstr "activité professionnelle" msgstr "activité professionnelle"
#: apps/registration/models.py:147 #: apps/registration/models.py:151
msgid "coach registration" msgid "coach registration"
msgstr "inscription d'encadrant" msgstr "inscription d'encadrant"
#: apps/registration/models.py:148 #: apps/registration/models.py:152
msgid "coach registrations" msgid "coach registrations"
msgstr "inscriptions d'encadrants" msgstr "inscriptions d'encadrants"
#: apps/registration/models.py:153 #: apps/registration/models.py:157
msgid "role of the administrator" msgid "role of the administrator"
msgstr "rôle de l'administrateur" msgstr "rôle de l'administrateur"
#: apps/registration/models.py:158 #: apps/registration/models.py:162
msgid "admin" msgid "admin"
msgstr "admin" msgstr "admin"
#: apps/registration/models.py:166 #: apps/registration/models.py:170
msgid "admin registration" msgid "admin registration"
msgstr "inscription d'administrateur" msgstr "inscription d'administrateur"
#: apps/registration/models.py:167 #: apps/registration/models.py:171
msgid "admin registrations" msgid "admin registrations"
msgstr "inscriptions d'administrateur" msgstr "inscriptions d'administrateur"
#: apps/registration/tables.py:11
#, fuzzy
#| msgid "Last name:"
msgid "last name"
msgstr "Nom de famille :"
#: apps/registration/templates/registration/email_validation_complete.html:15 #: apps/registration/templates/registration/email_validation_complete.html:15
msgid "Your email have successfully been validated." msgid "Your email have successfully been validated."
msgstr "Votre email a été validé avec succès." msgstr "Votre email a été validé avec succès."
@ -617,7 +625,7 @@ msgid "Your password has been set. You may go ahead and log in now."
msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter." msgstr "Votre mot de passe a été changé. Vous pouvez désormais vous connecter."
#: apps/registration/templates/registration/password_reset_complete.html:10 #: apps/registration/templates/registration/password_reset_complete.html:10
#: templates/base.html:113 templates/base.html:197 templates/base.html:198 #: templates/base.html:123 templates/base.html:210 templates/base.html:211
#: templates/registration/login.html:7 templates/registration/login.html:8 #: templates/registration/login.html:7 templates/registration/login.html:8
#: templates/registration/login.html:25 #: templates/registration/login.html:25
msgid "Log in" msgid "Log in"
@ -772,14 +780,18 @@ msgstr "Mail de confirmation de l'adresse mail envoyé"
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "Renvoyé le lien de validation de l'adresse mail" msgstr "Renvoyé le lien de validation de l'adresse mail"
#: corres2math/settings.py:150 #: corres2math/settings.py:151
msgid "English" msgid "English"
msgstr "Anglais" msgstr "Anglais"
#: corres2math/settings.py:151 #: corres2math/settings.py:152
msgid "French" msgid "French"
msgstr "Français" msgstr "Français"
#: corres2math/views.py:12
msgid "Only administrators are allowed to perform a full research."
msgstr "Seuls les administrateurs sont autorisés à effectuer une recherche."
#: templates/400.html:6 #: templates/400.html:6
msgid "Bad request" msgid "Bad request"
msgstr "Requête invalide" msgstr "Requête invalide"
@ -853,23 +865,27 @@ msgstr "Faire un don"
msgid "Administration" msgid "Administration"
msgstr "Administration" msgstr "Administration"
#: templates/base.html:104 #: templates/base.html:105
msgid "Search..."
msgstr "Chercher ..."
#: templates/base.html:114
msgid "Return to admin view" msgid "Return to admin view"
msgstr "Retourner à l'interface administrateur" msgstr "Retourner à l'interface administrateur"
#: templates/base.html:109 #: templates/base.html:119
msgid "Register" msgid "Register"
msgstr "S'inscrire" msgstr "S'inscrire"
#: templates/base.html:125 #: templates/base.html:135
msgid "My account" msgid "My account"
msgstr "Mon compte" msgstr "Mon compte"
#: templates/base.html:128 #: templates/base.html:138
msgid "Log out" msgid "Log out"
msgstr "Déconnexion" msgstr "Déconnexion"
#: templates/base.html:144 #: templates/base.html:154
#, python-format #, python-format
msgid "" msgid ""
"Your email address is not validated. Please click on the link you received " "Your email address is not validated. Please click on the link you received "
@ -880,10 +896,14 @@ msgstr ""
"avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur <a href=" "avez reçu par mail. Vous pouvez renvoyer un mail en cliquant sur <a href="
"\"%(send_email_url)s\">ce lien</a>." "\"%(send_email_url)s\">ce lien</a>."
#: templates/base.html:167 #: templates/base.html:177
msgid "Contact us" msgid "Contact us"
msgstr "Nous contacter" msgstr "Nous contacter"
#: templates/base.html:208
msgid "Search results"
msgstr "Résultats de la recherche"
#: templates/registration/logged_out.html:8 #: templates/registration/logged_out.html:8
msgid "Thanks for spending some quality time with the Web site today." msgid "Thanks for spending some quality time with the Web site today."
msgstr "Merci d'avoir utilisé la plateforme des Correspondances." msgstr "Merci d'avoir utilisé la plateforme des Correspondances."
@ -904,3 +924,15 @@ msgstr ""
#: templates/registration/login.html:23 #: templates/registration/login.html:23
msgid "Forgotten your password or username?" msgid "Forgotten your password or username?"
msgstr "Mot de passe oublié ?" msgstr "Mot de passe oublié ?"
#: templates/search/search.html:6 templates/search/search.html:11
msgid "Search"
msgstr "Chercher"
#: templates/search/search.html:16
msgid "Results"
msgstr "Résultats"
#: templates/search/search.html:26
msgid "No results found."
msgstr "Aucun résultat."

View File

@ -3,6 +3,7 @@ django-bootstrap-datepicker-plus
django-crispy-forms django-crispy-forms
django-extensions django-extensions
django-filter django-filter
django-haystack
django-mailer django-mailer
django-polymorphic django-polymorphic
django-tables2 django-tables2
@ -10,4 +11,5 @@ djangorestframework
django-rest-polymorphic django-rest-polymorphic
ptpython ptpython
python-magic python-magic
gunicorn gunicorn
whoosh

View File

@ -99,6 +99,16 @@
{% endif %} {% endif %}
</ul> </ul>
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
{% if user.registration.is_admin %}
<form class="navbar-form" role="search" onsubmit="event.preventDefault()">
<div class="input-group">
<input type="text" class="form-control" placeholder="{% trans "Search..." %}" name="q" id="search-term" value="{{ request.GET.q }}">
<div class="input-group-btn">
<button class="btn btn-default" data-toggle="modal" data-target="#searchModal"><i class="fa fa-search"></i></button>
</div>
</div>
</form>
{% endif %}
{% if "_fake_user_id" in request.session %} {% if "_fake_user_id" in request.session %}
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="{% url "member:reset_admin" %}?path={{ request.path }}"><i class="fas fa-tools"></i> {% trans "Return to admin view" %}</a> <a class="nav-link" href="{% url "member:reset_admin" %}?path={{ request.path }}"><i class="fas fa-tools"></i> {% trans "Return to admin view" %}</a>
@ -194,6 +204,9 @@
</div> </div>
</footer> </footer>
{% trans "Search results" as modal_title %}
{% include "base_modal.html" with modal_id="search" modal_form_method="get" modal_additional_class="modal-lg" %}
{% trans "Log in" as modal_title %} {% trans "Log in" as modal_title %}
{% trans "Log in" as modal_button %} {% trans "Log in" as modal_button %}
{% url "login" as modal_action %} {% url "login" as modal_action %}
@ -214,6 +227,11 @@
$(".invalid-feedback").addClass("d-block"); $(".invalid-feedback").addClass("d-block");
$(document).ready(function () { $(document).ready(function () {
$('button[data-target="#searchModal"]').click(function() {
let modalBody = $("#searchModal div.modal-body");
let q = encodeURI($("#search-term").val());
modalBody.load("{% url "haystack_search" %}?q=" + q + " #search-results");
});
$('a[data-target="#loginModal"]').click(function() { $('a[data-target="#loginModal"]').click(function() {
let modalBody = $("#loginModal div.modal-body"); let modalBody = $("#loginModal div.modal-body");
if (!modalBody.html().trim()) if (!modalBody.html().trim())

View File

@ -1,8 +1,8 @@
{% load i18n %} {% load i18n %}
<div id="{{ modal_id }}Modal" class="modal fade" tabindex="-1" role="dialog"> <div id="{{ modal_id }}Modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document"> <div class="modal-dialog {{ modal_additional_class }}" role="document">
<form method="post" action="{{ modal_action }}" enctype="{{ modal_enctype|default:"application/x-www-form-urlencoded" }}"> <form method="{{ modal_form_method|default:"post" }}" action="{{ modal_action }}" enctype="{{ modal_enctype|default:"application/x-www-form-urlencoded" }}">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">{{ modal_title }}</h5> <h5 class="modal-title">{{ modal_title }}</h5>
@ -12,7 +12,9 @@
</div> </div>
<div class="modal-body">{{ modal_content }}</div> <div class="modal-body">{{ modal_content }}</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-{{ modal_button_type|default:"primary" }}">{{ modal_button }}</button> {% if modal_button %}
<button type="submit" class="btn btn-{{ modal_button_type|default:"primary" }}">{{ modal_button }}</button>
{% endif %}
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% load crispy_forms_filters highlight i18n search_results_tables django_tables2 %}
{% block content %}
<h2>{% trans "Search" %}</h2>
<form>
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary">{% trans "Search" %}</button>
</form>
<hr>
<h3>{% trans "Results" %}</h3>
<div id="search-results">
{% regroup object_list by model_name as categories %}
{% for category in categories %}
<h4>{% trans category.grouper|capfirst %}</h4>
{% with table=category.list|search_table %}
{% render_table table %}
{% endwith %}
{% empty %}
<p>{% trans "No results found." %}</p>
{% endfor %}
</div>
{% endblock %}