parent
74fe6cb86f
commit
7a9840d5cb
@ -24,7 +24,7 @@ from django.core.files import File
|
|||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.db import connection, models
|
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.db.models.functions import Lower
|
||||||
from django.template.defaultfilters import date as _date
|
from django.template.defaultfilters import date as _date
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
@ -40,8 +40,13 @@ from django_resized import ResizedImageField
|
|||||||
from icalendar import Calendar as icalCal
|
from icalendar import Calendar as icalCal
|
||||||
from icalendar import Event as icalEvent
|
from icalendar import Event as icalEvent
|
||||||
from location_field.models.spatial import LocationField
|
from location_field.models.spatial import LocationField
|
||||||
|
from django.contrib.gis.db.models.functions import Distance
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.db.models.signals import post_save
|
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 .calendar import CalendarDay
|
||||||
from .import_tasks.extractor import Extractor
|
from .import_tasks.extractor import Extractor
|
||||||
@ -914,6 +919,7 @@ class Event(models.Model):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.processing_user = None
|
self.processing_user = None
|
||||||
|
self._proposed_events = None
|
||||||
self._messages = []
|
self._messages = []
|
||||||
|
|
||||||
def get_import_messages(self):
|
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):
|
def chronology_dates(self):
|
||||||
return self.chronology(True)
|
return self.chronology(True)
|
||||||
|
|
||||||
|
@ -1238,7 +1238,6 @@ article form div .recurrence-widget {
|
|||||||
|
|
||||||
article>article {
|
article>article {
|
||||||
margin: 3em 0.5em;
|
margin: 3em 0.5em;
|
||||||
padding: 0.6em 0.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2156,3 +2155,34 @@ dialog {
|
|||||||
margin-bottom: 0.5em;
|
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;
|
||||||
|
}
|
||||||
|
@ -128,6 +128,17 @@
|
|||||||
{% include "agenda_culturel/event-info-inc.html" with allbutdates=1 %}
|
{% include "agenda_culturel/event-info-inc.html" with allbutdates=1 %}
|
||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if event.proposed_events %}
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<h2>Événements similaires</h2>
|
||||||
|
<p>Cet événement vous intéresse ? Vous pourriez aussi aimer les événements ci-dessous.</p>
|
||||||
|
</header>
|
||||||
|
{% for pe in event.proposed_events %}
|
||||||
|
{% include "agenda_culturel/single-event/event-slim-inc.html" with event=pe %}
|
||||||
|
{% endfor %}
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
||||||
{% cache cache_timeout event_aside user.is_authenticated event.pk %}
|
{% cache cache_timeout event_aside user.is_authenticated event.pk %}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
{% load tag_extra %}
|
||||||
|
{% load utils_extra %}
|
||||||
|
<div class="slim-description">
|
||||||
|
<div class="date-heure">{% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %}</div>
|
||||||
|
<div class="texte-principal">
|
||||||
|
<h3>
|
||||||
|
<a href="{{ pe.get_absolute_url }}">{{ pe.title }}</a>
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
{% if pe.exact_location %}
|
||||||
|
{% picto_from_name "map-pin" %}
|
||||||
|
{{ pe.exact_location.name }}, {{ pe.exact_location.city }}
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
{% if pe.location %}
|
||||||
|
{% picto_from_name "map-pin" %} {{ pe.location }}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="description">
|
||||||
|
{{ event.description |truncatewords:15 }}
|
||||||
|
<p class="tag-list">
|
||||||
|
{% for tag in event.sorted_tags %}
|
||||||
|
<a href="{% url 'view_tag' tag|prepare_tag %}"
|
||||||
|
role="button"
|
||||||
|
class="small-cat">{{ tag|tw_highlight }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if event.image or event.local_image %}
|
||||||
|
<div class="illustration">
|
||||||
|
<img src="{% if pe.local_image %}{{ event.local_image.url }}{% else %}{{ event.image }}{% endif %}"
|
||||||
|
alt="{{ event.image_alt }}" />
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
Loading…
x
Reference in New Issue
Block a user