Amélioration affichage statistiques
This commit is contained in:
parent
f03ebb6458
commit
bfd8a5f3c0
@ -25,7 +25,7 @@ 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
|
||||||
from django.db.models.functions import Lower, ExtractDay
|
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
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
@ -42,8 +42,6 @@ from icalendar import Event as icalEvent
|
|||||||
from location_field.models.spatial import LocationField
|
from location_field.models.spatial import LocationField
|
||||||
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.aggregates import StdDev
|
|
||||||
from django.db.models import Avg, Max, Min
|
|
||||||
|
|
||||||
from .calendar import CalendarDay
|
from .calendar import CalendarDay
|
||||||
from .import_tasks.extractor import Extractor
|
from .import_tasks.extractor import Extractor
|
||||||
@ -51,18 +49,8 @@ from .import_tasks.generic_extractors.fbevent import (
|
|||||||
CExtractor as FacebookEventExtractor,
|
CExtractor as FacebookEventExtractor,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.db.models import Aggregate, FloatField
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Median(Aggregate):
|
|
||||||
function = "PERCENTILE_CONT"
|
|
||||||
name = "median"
|
|
||||||
output_field = FloatField()
|
|
||||||
template = "%(function)s(0.5) WITHIN GROUP (ORDER BY %(expressions)s)"
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Useful for translation
|
# Useful for translation
|
||||||
@ -2747,46 +2735,6 @@ class RecurrentImport(models.Model):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_foresight_quality_internal(qs):
|
|
||||||
limit = datetime.now() + timedelta(days=-30)
|
|
||||||
|
|
||||||
qs = qs.filter(start_day__gte=F("created_date")).annotate(
|
|
||||||
foresight=ExtractDay(F("start_day") - F("created_date"))
|
|
||||||
)
|
|
||||||
|
|
||||||
stats = qs.filter().aggregate(
|
|
||||||
minimum=Min("foresight"),
|
|
||||||
maximum=Max("foresight"),
|
|
||||||
mean=Avg("foresight"),
|
|
||||||
median=Median("foresight"),
|
|
||||||
stdev=StdDev("foresight"),
|
|
||||||
)
|
|
||||||
|
|
||||||
statsm = qs.filter(created_date__gte=limit).aggregate(
|
|
||||||
minimum=Min("foresight"),
|
|
||||||
maximum=Max("foresight"),
|
|
||||||
mean=Avg("foresight"),
|
|
||||||
median=Median("foresight"),
|
|
||||||
stdev=StdDev("foresight"),
|
|
||||||
)
|
|
||||||
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
_(x),
|
|
||||||
round(stats[x], 2) if stats[x] is not None else "-",
|
|
||||||
round(statsm[x], 2) if statsm[x] is not None else "-",
|
|
||||||
]
|
|
||||||
for x in stats
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_global_foresight_quality():
|
|
||||||
return RecurrentImport._get_foresight_quality_internal(Event.objects)
|
|
||||||
|
|
||||||
def get_foresight_quality(self):
|
|
||||||
return RecurrentImport._get_foresight_quality_internal(
|
|
||||||
Event.objects.filter(import_sources__contains=[self.source])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BatchImportation(models.Model):
|
class BatchImportation(models.Model):
|
||||||
class STATUS(models.TextChoices):
|
class STATUS(models.TextChoices):
|
||||||
|
@ -85,33 +85,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</header>
|
</header>
|
||||||
{% with object.get_foresight_quality as stat %}
|
<div class="buttons slide-buttons">
|
||||||
{% if stat|length > 0 %}
|
<a href="{% url 'stats_rimport' object.pk %}" role="button">Statistiques {% picto_from_name "bar-chart-2" %}</a>
|
||||||
<h2>Qualité de l'anticipation</h2>
|
</div>
|
||||||
<p>
|
|
||||||
On s'intéresse à la différence entre la date de publication d'un événement et la date effective de l'événement. Plus le nombre de jours qui les sépare est élevé, plus
|
|
||||||
la source anticipe ses événements, et peut être considérée comme une source fiable. On ignore les événements ajoutés à l'agenda alors que l'événement a déjà eu lieu.
|
|
||||||
On affiche cette information pour les imports depuis le début de son
|
|
||||||
intégration à l'agenda, mais aussi pour le dernier mois.
|
|
||||||
</p>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<th class="label"></th>
|
|
||||||
{% for v in stat %}<th>{{ v.0 }}</th>{% endfor %}
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th class="label">écart (en jours)</th>
|
|
||||||
{% for v in stat %}<th>{{ v.1 }}</th>{% endfor %}
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th class="label">écart du dernier mois (en jours)</th>
|
|
||||||
{% for v in stat %}<th>{{ v.2 }}</th>{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
<h2>Liste des imports</h2>
|
<h2>Liste des imports</h2>
|
||||||
{% include "agenda_culturel/batch-imports-inc.html" with objects=paginator_filter %}
|
{% include "agenda_culturel/batch-imports-inc.html" with objects=paginator_filter %}
|
||||||
<footer>
|
<footer>
|
||||||
|
@ -0,0 +1,234 @@
|
|||||||
|
{% extends "agenda_culturel/page.html" %}
|
||||||
|
{% block title %}
|
||||||
|
{% block og_title %}Statistiques de l'import récurrent #{{ object.pk }}{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
{% load cat_extra %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load utils_extra %}
|
||||||
|
{% load static %}
|
||||||
|
{% load tag_extra %}
|
||||||
|
{% block entete_header %}
|
||||||
|
{% css_categories %}
|
||||||
|
<script src="{% static 'js/d3.v7.min.js' %}"></script>
|
||||||
|
<script src="{% static 'cal-heatmap/cal-heatmap.min.js' %}"></script>
|
||||||
|
<script src="{% static 'cal-heatmap/plugins/CalendarLabel.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js/popper.min.js' %}"></script>
|
||||||
|
<script src="{% static 'cal-heatmap/plugins/Tooltip.min.js' %}"></script>
|
||||||
|
<link rel="stylesheet" href="{% static 'cal-heatmap/cal-heatmap.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
{% block sidemenu-bouton %}
|
||||||
|
<li>
|
||||||
|
<a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a>
|
||||||
|
</li>
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="grid two-columns">
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<div class="buttons left-buttons">
|
||||||
|
<a href="{% url 'recurrent_imports' %}" role="button">< Tous les imports récurrents</a>
|
||||||
|
<a href="{% url 'recent' %}?import_sources={{ object.pk }}"
|
||||||
|
role="button">Voir les événements {% picto_from_name "calendar" %}</a>
|
||||||
|
</div>
|
||||||
|
<div class="buttons slide-buttons">
|
||||||
|
<a href="{% url 'run_rimport' object.pk %}" role="button">Exécuter {% picto_from_name "download-cloud" %}</a>
|
||||||
|
<a href="{% url 'edit_rimport' object.pk %}" role="button">Modifier {% picto_from_name "edit" %}</a>
|
||||||
|
<a href="{% url 'delete_rimport' object.pk %}" role="button">Supprimer {% picto_from_name "trash" %}</a>
|
||||||
|
</div>
|
||||||
|
<h1>Statistiques de l'import récurrent #{{ object.pk }}</h1>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Nom :</strong> {{ object.name }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Processeur :</strong> {{ object.processor }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Téléchargeur :</strong> {{ object.downloader }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Recurrence :</strong> {% trans object.recurrence %}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Source :</strong> <a href="{{ object.source }}">{{ object.source }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Adresse naviguable :</strong> <a href="{{ object.browsable_url }}">{{ object.browsable_url }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Valeurs par défaut :</strong>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Publié :</strong> {{ object.defaultPublished|yesno:"Oui,Non" }}
|
||||||
|
</li>
|
||||||
|
{% if object.defaultLocation %}
|
||||||
|
<li>
|
||||||
|
<strong>Localisation
|
||||||
|
{% if object.forceLocation %}(forcée){% endif %}
|
||||||
|
:</strong> {{ object.defaultLocation }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
<strong>Catégorie :</strong> {{ object.defaultCategory }}
|
||||||
|
</li>
|
||||||
|
{% if object.defaultOrganiser %}
|
||||||
|
<li>
|
||||||
|
<strong>Organisateur :</strong> <a href="{{ object.defaultOrganiser.get_absolute_url }}">{{ object.defaultOrganiser }}</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
<strong>Étiquettes :</strong>
|
||||||
|
{% for tag in object.defaultTags %}
|
||||||
|
<a href="{% url 'view_tag' tag %}">{{ tag|tw_highlight }}</a>
|
||||||
|
{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</header>
|
||||||
|
<div class="buttons slide-buttons">
|
||||||
|
<a href="{% url 'view_rimport' object.pk %}" role="button">Liste des récents {% picto_from_name "eye" %}</a>
|
||||||
|
</div>
|
||||||
|
<p>On retrouve sur cette page une synthèse des événements récupérés grâce à cet import récurrent.</p>
|
||||||
|
<h2>Par date de l'événement</h2>
|
||||||
|
<p>Pour chaque date, on retrouve le nombre d'événements qui débutent à cette date (sauf événements récurrents).</p>
|
||||||
|
<div class="large-table">
|
||||||
|
<div id="cal-heatmap-startday"></div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
On retrouve une synthèse par mois du tableau précédent, sous forme d'une moyenne du nombre d'événements par jour pour chaque mois.
|
||||||
|
</p>
|
||||||
|
{% include "agenda_culturel/statistics_per_month.html" with data=stats_months_by_startday %}
|
||||||
|
<h2>Par jour de création sur l'agenda</h2>
|
||||||
|
<p>
|
||||||
|
Pour chaque date, on retrouve le nombre d'événements qui ont été créé ce jour-là, par import automatique ou par création manuelle.
|
||||||
|
Ce nombre ne tient pas en compte les événements qui sont des doublons d'événements déjà importés, ni des événements que l'équipe de
|
||||||
|
modération a choisi de ne pas conserver.
|
||||||
|
</p>
|
||||||
|
<div class="large-table">
|
||||||
|
<div id="cal-heatmap-creation"></div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
On retrouve une synthèse par mois du tableau précédent, sous forme d'une moyenne du nombre de création d'événements par jour pour chaque mois.
|
||||||
|
</p>
|
||||||
|
{% include "agenda_culturel/statistics_per_month.html" with data=stats_months_by_creation %}
|
||||||
|
<h2>Qualité de l'anticipation</h2>
|
||||||
|
<p>
|
||||||
|
On s'intéresse à la différence entre la date de publication d'un événement et la date effective de l'événement. Plus le nombre de jours qui les sépare est élevé, plus
|
||||||
|
les sources anticipent leurs événements. On ignore les événements ajoutés à l'agenda alors que l'événement a déjà eu lieu.
|
||||||
|
On affiche cette information pour les imports depuis le début de leur
|
||||||
|
intégration à l'agenda, mais aussi pour le dernier mois.
|
||||||
|
</p>
|
||||||
|
<div class="large-table">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<th class="label"></th>
|
||||||
|
{% for v in stats_foresight %}<th>{{ v.0 }}</th>{% endfor %}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="label">écart (en jours)</th>
|
||||||
|
{% for v in stats_foresight %}<th>{{ v.1 }}</th>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="label">écart du dernier mois (en jours)</th>
|
||||||
|
{% for v in stats_foresight %}<th>{{ v.2 }}</th>{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h2>Par ville</h2>
|
||||||
|
<p>Nombre d'événements référencés par ville.</p>
|
||||||
|
<ul>
|
||||||
|
{% for v in nb_by_city %}
|
||||||
|
<li>
|
||||||
|
<strong>{{ v.city }} :</strong> {{ v.total }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
{% include "agenda_culturel/side-nav.html" with current="rimports" %}
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// prepare data
|
||||||
|
const data_startday = [
|
||||||
|
{% for s in stats_by_startday %}
|
||||||
|
{% if not forloop.first %},{% endif %}
|
||||||
|
{date: new Date('{{ s.day.isoformat }}'), value: {{ s.total}}}
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
const data_creation = [
|
||||||
|
{% for s in stats_by_creation %}
|
||||||
|
{% if not forloop.first %},{% endif %}
|
||||||
|
{date: new Date('{{ s.day.isoformat }}'), value: {{ s.total}}}
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
|
||||||
|
// define parameters
|
||||||
|
const tooltip = {text: function (timestamp, value, dayjsDate) {
|
||||||
|
if (value != null)
|
||||||
|
return `${dayjsDate.format('DD/MM/YYYY')}: ${value}`;
|
||||||
|
else
|
||||||
|
return `${dayjsDate.format('DD/MM/YYYY')}`;
|
||||||
|
}};
|
||||||
|
const calendarlabel = { position: 'left', key: 'left', text: () => ['lun', '', 'mer', '', 'ven', '', 'dim'] };
|
||||||
|
|
||||||
|
// set options
|
||||||
|
var options = {
|
||||||
|
date: {
|
||||||
|
locale: 'fr',
|
||||||
|
start: new Date("{{ first_by_startday.isoformat }}"),
|
||||||
|
highlight: new Date()
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
type: "month",
|
||||||
|
label: {text: 'MMM YY'},
|
||||||
|
padding: [3, 3, 3, 3]
|
||||||
|
},
|
||||||
|
subDomain: {
|
||||||
|
type: "day",
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
radius: 2,
|
||||||
|
label : function (timestamp, value) { if (value == null) return ''; else return `${value}`; }
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
source: data_startday,
|
||||||
|
defaultValue: null,
|
||||||
|
x: "date",
|
||||||
|
y: "value"
|
||||||
|
},
|
||||||
|
theme: (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? "dark" : "light",
|
||||||
|
itemSelector: '#cal-heatmap-startday',
|
||||||
|
animationDuration: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// create first heatmap
|
||||||
|
const cal_startday = new CalHeatmap();
|
||||||
|
cal_startday.paint(options, [
|
||||||
|
[CalendarLabel, calendarlabel],
|
||||||
|
[Tooltip, tooltip]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// create second heatmap
|
||||||
|
options.itemSelector = '#cal-heatmap-creation';
|
||||||
|
options.date.start = new Date("{{ first_by_creation.isoformat }}");
|
||||||
|
options.data.source = data_creation;
|
||||||
|
const cal_creation = new CalHeatmap();
|
||||||
|
cal_creation.paint(options, [
|
||||||
|
[CalendarLabel, calendarlabel],
|
||||||
|
[Tooltip, tooltip]
|
||||||
|
]);
|
||||||
|
|
||||||
|
cal_startday.on('click', (event, timestamp, value) => {
|
||||||
|
const d = new Date(timestamp);
|
||||||
|
const url = "{% url 'day_view' 0 0 0 %}".replace("/0/", "/" + d.getFullYear() + "/").replace("/0/", "/" + (d.getMonth() + 1) + "/").replace("/0/", "/" + d.getDate() + "/");
|
||||||
|
window.location = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -327,6 +327,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("rimports/add", RecurrentImportCreateView.as_view(), name="add_rimport"),
|
path("rimports/add", RecurrentImportCreateView.as_view(), name="add_rimport"),
|
||||||
path("rimports/<int:pk>/view", view_rimport, name="view_rimport"),
|
path("rimports/<int:pk>/view", view_rimport, name="view_rimport"),
|
||||||
|
path("rimports/<int:pk>/stats", statistics, name="stats_rimport"),
|
||||||
path(
|
path(
|
||||||
"rimports/<int:pk>/edit",
|
"rimports/<int:pk>/edit",
|
||||||
RecurrentImportUpdateView.as_view(),
|
RecurrentImportUpdateView.as_view(),
|
||||||
|
@ -38,6 +38,10 @@ from django.views.generic.edit import (
|
|||||||
UpdateView,
|
UpdateView,
|
||||||
)
|
)
|
||||||
from honeypot.decorators import check_honeypot
|
from honeypot.decorators import check_honeypot
|
||||||
|
from django.db.models.aggregates import StdDev
|
||||||
|
from django.db.models import Avg, Max, Min
|
||||||
|
from django.db.models import Aggregate, FloatField
|
||||||
|
from django.db.models.functions import ExtractDay
|
||||||
|
|
||||||
from .calendar import CalendarDay, CalendarList, CalendarMonth, CalendarWeek
|
from .calendar import CalendarDay, CalendarList, CalendarMonth, CalendarWeek
|
||||||
from .celery import app as celery_app
|
from .celery import app as celery_app
|
||||||
@ -103,6 +107,13 @@ from .utils import PlaceGuesser
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Median(Aggregate):
|
||||||
|
function = "PERCENTILE_CONT"
|
||||||
|
name = "median"
|
||||||
|
output_field = FloatField()
|
||||||
|
template = "%(function)s(0.5) WITHIN GROUP (ORDER BY %(expressions)s)"
|
||||||
|
|
||||||
|
|
||||||
class PaginatorFilter(Paginator):
|
class PaginatorFilter(Paginator):
|
||||||
def __init__(self, filter, nb, request):
|
def __init__(self, filter, nb, request):
|
||||||
self.request = request
|
self.request = request
|
||||||
@ -2765,13 +2776,21 @@ def view_tag(request, t, past=False):
|
|||||||
return render(request, "agenda_culturel/tag.html", context)
|
return render(request, "agenda_culturel/tag.html", context)
|
||||||
|
|
||||||
|
|
||||||
def statistics(request):
|
def statistics(request, pk=None):
|
||||||
|
if pk is not None:
|
||||||
|
rimport = RecurrentImport.objects.filter(pk=pk)
|
||||||
|
source = rimport.values("source").first()["source"]
|
||||||
|
qs = Event.objects.filter(import_sources__contains=[source])
|
||||||
|
else:
|
||||||
|
rimport = None
|
||||||
|
qs = Event.objects
|
||||||
|
|
||||||
stats = {}
|
stats = {}
|
||||||
stats_months = {}
|
stats_months = {}
|
||||||
first = {}
|
first = {}
|
||||||
last = {}
|
last = {}
|
||||||
|
|
||||||
ev_published = Event.objects.filter(
|
ev_published = qs.filter(
|
||||||
Q(status=Event.STATUS.PUBLISHED)
|
Q(status=Event.STATUS.PUBLISHED)
|
||||||
& (
|
& (
|
||||||
Q(other_versions__isnull=True)
|
Q(other_versions__isnull=True)
|
||||||
@ -2817,7 +2836,36 @@ def statistics(request):
|
|||||||
.order_by("-total")
|
.order_by("-total")
|
||||||
)
|
)
|
||||||
|
|
||||||
stats_foresight = RecurrentImport.get_global_foresight_quality()
|
limit = datetime.now() + timedelta(days=-30)
|
||||||
|
|
||||||
|
stat_qs = qs.filter(start_day__gte=F("created_date")).annotate(
|
||||||
|
foresight=ExtractDay(F("start_day") - F("created_date"))
|
||||||
|
)
|
||||||
|
|
||||||
|
statsa = stat_qs.filter().aggregate(
|
||||||
|
minimum=Min("foresight"),
|
||||||
|
maximum=Max("foresight"),
|
||||||
|
mean=Avg("foresight"),
|
||||||
|
median=Median("foresight"),
|
||||||
|
stdev=StdDev("foresight"),
|
||||||
|
)
|
||||||
|
|
||||||
|
statsm = stat_qs.filter(created_date__gte=limit).aggregate(
|
||||||
|
minimum=Min("foresight"),
|
||||||
|
maximum=Max("foresight"),
|
||||||
|
mean=Avg("foresight"),
|
||||||
|
median=Median("foresight"),
|
||||||
|
stdev=StdDev("foresight"),
|
||||||
|
)
|
||||||
|
|
||||||
|
stats_foresight = [
|
||||||
|
[
|
||||||
|
_(x),
|
||||||
|
round(statsa[x], 2) if statsa[x] is not None else "-",
|
||||||
|
round(statsm[x], 2) if statsm[x] is not None else "-",
|
||||||
|
]
|
||||||
|
for x in statsa
|
||||||
|
]
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"stats_by_startday": stats["start_day"],
|
"stats_by_startday": stats["start_day"],
|
||||||
@ -2830,8 +2878,13 @@ def statistics(request):
|
|||||||
"last_by_creation": last["created_date__date"],
|
"last_by_creation": last["created_date__date"],
|
||||||
"nb_by_city": nb_by_city,
|
"nb_by_city": nb_by_city,
|
||||||
"stats_foresight": stats_foresight,
|
"stats_foresight": stats_foresight,
|
||||||
|
"object": rimport.first() if rimport else None,
|
||||||
}
|
}
|
||||||
return render(request, "agenda_culturel/statistics.html", context)
|
|
||||||
|
if pk is None:
|
||||||
|
return render(request, "agenda_culturel/statistics.html", context)
|
||||||
|
else:
|
||||||
|
return render(request, "agenda_culturel/rimport-statistics.html", context)
|
||||||
|
|
||||||
|
|
||||||
def tag_list(request):
|
def tag_list(request):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user