Événements similaires
+Cet événement vous intéresse ? Vous pourriez aussi aimer les événements ci-dessous.
+diff --git a/src/agenda_culturel/models.py b/src/agenda_culturel/models.py
index e571def..18e0523 100644
--- a/src/agenda_culturel/models.py
+++ b/src/agenda_culturel/models.py
@@ -24,7 +24,7 @@ from django.core.files import File
from django.core.files.storage import default_storage
from django.core.mail import send_mail
from django.db import connection, models
-from django.db.models import Count, F, Func, OuterRef, Q, Subquery
+from django.db.models import Count, F, Func, OuterRef, Q, Subquery, Value
from django.db.models.functions import Lower
from django.template.defaultfilters import date as _date
from django.template.defaultfilters import slugify
@@ -40,8 +40,13 @@ from django_resized import ResizedImageField
from icalendar import Calendar as icalCal
from icalendar import Event as icalEvent
from location_field.models.spatial import LocationField
+from django.contrib.gis.db.models.functions import Distance
from django.dispatch import receiver
from django.db.models.signals import post_save
+from django.db.models.functions import Now
+from django.db.models.functions import ExtractDay
+from django.db.models.expressions import RawSQL
+
from .calendar import CalendarDay
from .import_tasks.extractor import Extractor
@@ -914,6 +919,7 @@ class Event(models.Model):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.processing_user = None
+ self._proposed_events = None
self._messages = []
def get_import_messages(self):
@@ -1047,6 +1053,78 @@ class Event(models.Model):
),
]
+ def proposed_events(self):
+ threshold_distance = 30000
+ min_tags = 1
+
+ if self._proposed_events is None:
+ qs = (
+ Event.objects.filter(
+ Q(start_day__gte=Now()) & Q(category=self.category) & ~Q(pk=self.pk)
+ )
+ .filter(
+ Q(other_versions__isnull=True)
+ | Q(other_versions__representative=F("pk"))
+ | Q(other_versions__representative__isnull=True)
+ )
+ .filter(status=Event.STATUS.PUBLISHED)
+ )
+
+ if len(self.tags) > 0:
+ qs = qs.annotate(
+ overlap_tags=RawSQL(
+ sql="ARRAY(select UNNEST(%s::text[]) INTERSECT select UNNEST(tags))",
+ params=(self.tags,),
+ output_field=ArrayField(models.CharField(max_length=50)),
+ )
+ ).annotate(
+ overlap_tags_count=Func(
+ F("overlap_tags"),
+ function="CARDINALITY",
+ output_field=models.IntegerField(),
+ )
+ )
+ else:
+ qs = qs.annotate(overlap_tags_count=Value(1))
+ qs = (
+ qs.filter(overlap_tags_count__gte=min_tags)
+ .annotate(
+ distance=Distance(
+ F("exact_location__location"), self.exact_location.location
+ )
+ )
+ .filter(distance__lte=threshold_distance)
+ .annotate(
+ nbday_distance=Func(
+ (ExtractDay(F("start_day") - self.start_day)), function="ABS"
+ )
+ )
+ .annotate(similarity_title=TrigramSimilarity("title", self.title))
+ .annotate(
+ similarity_description=TrigramSimilarity(
+ "description", self.description
+ )
+ )
+ .annotate(
+ score=F("overlap_tags_count") * 30
+ + F("similarity_title") * 2
+ + F("similarity_description") * 10
+ + 10 / (F("distance") + 1)
+ + 30 / (F("nbday_distance") + 1)
+ )
+ .order_by("-score")[:10]
+ )
+
+ self._proposed_events = qs.only(
+ "title", "start_day", "start_time", "exact_location", "location"
+ )
+ self._proposed_events = sorted(
+ self._proposed_events,
+ key=lambda e: (e.start_day, e.start_time, e.title),
+ )
+
+ return self._proposed_events
+
def chronology_dates(self):
return self.chronology(True)
diff --git a/src/agenda_culturel/static/style.scss b/src/agenda_culturel/static/style.scss
index 7f56d37..03d70ac 100644
--- a/src/agenda_culturel/static/style.scss
+++ b/src/agenda_culturel/static/style.scss
@@ -1238,7 +1238,6 @@ article form div .recurrence-widget {
article>article {
margin: 3em 0.5em;
- padding: 0.6em 0.2em;
}
@@ -2156,3 +2155,34 @@ dialog {
margin-bottom: 0.5em;
}
}
+
+.slim-description {
+ font-size: 85%;
+ clear: both;
+ .ephemeris {
+ font-size: 70%;
+ max-width: 10em;
+ }
+ display: grid;
+ grid-template-columns: auto 1fr;
+ .tag-list {
+ margin-bottom: 0em;
+ font-size: 80%;
+ }
+ .illustration {
+ grid-column: 1/3;
+ max-height: 5em;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ }
+ @media (min-width: 800px) {
+ grid-template-columns: auto 1fr auto;
+ .illustration {
+ max-height: 12em;
+ grid-column: 3/4;
+ width: 14em;
+ }
+ }
+ margin-bottom: 1em;
+}
diff --git a/src/agenda_culturel/templates/agenda_culturel/page-event.html b/src/agenda_culturel/templates/agenda_culturel/page-event.html
index fa97020..d42a0b0 100644
--- a/src/agenda_culturel/templates/agenda_culturel/page-event.html
+++ b/src/agenda_culturel/templates/agenda_culturel/page-event.html
@@ -128,6 +128,17 @@
{% include "agenda_culturel/event-info-inc.html" with allbutdates=1 %}
{% endif %}
+ {% if event.proposed_events %}
+ Cet événement vous intéresse ? Vous pourriez aussi aimer les événements ci-dessous.Événements similaires
+
+ {% if pe.exact_location %} + {% picto_from_name "map-pin" %} + {{ pe.exact_location.name }}, {{ pe.exact_location.city }} +
+ {% else %} + {% if pe.location %} + {% picto_from_name "map-pin" %} {{ pe.location }} + {% endif %} + {% endif %} +