Commit 64590231 authored by hypsug0's avatar hypsug0
Browse files

fix: widely improve perfs using select|prefetch_related (3x to 10x faster), to be continued

parent 9996aceb
......@@ -95,7 +95,7 @@ header h1 {
color: #fff;
height: 65px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
background-color: #473030;
}
.page-header h1 {
......
......@@ -594,7 +594,7 @@ const placeSearchBlock = () => {
$('#searchForm').on('click change', 'input, select', function () {
clearTimeout(debounce)
debounce = setTimeout(showValues, 2000)
debounce = setTimeout(showValues, 500)
})
showValues()
......@@ -990,7 +990,7 @@ const sightingSearchBlock = () => {
var debounce = null
$('#searchForm').on('click change', 'input, select', function () {
clearTimeout(debounce)
debounce = setTimeout(showValues, 2000)
debounce = setTimeout(showValues, 500)
})
showValues()
......
......@@ -4,6 +4,7 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Prefetch
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView
......@@ -89,7 +90,18 @@ class StudyList(LoginRequiredMixin, SingleTableView):
class StudyDetail(LoginRequiredMixin, DetailView):
queryset = Study.objects.select_related()
queryset = (
Study.objects.select_related("project_manager")
.select_related("created_by")
.select_related("updated_by")
.prefetch_related("sessions")
.prefetch_related(Prefetch("sessions__sighting_set"))
.prefetch_related(Prefetch("sessions__place"))
.prefetch_related(Prefetch("sessions__contact"))
.prefetch_related(Prefetch("sessions__sighting_set__codesp"))
.prefetch_related(Prefetch("sessions__main_observer"))
.prefetch_related(Prefetch("sessions__other_observer"))
)
template_name = "study_detail.html"
def get_context_data(self, **kwargs):
......
......@@ -42,7 +42,9 @@ class StudyTable(tables.Table):
</div>
</div>
"""
LINK_NAME = '<a href="{% url "management:url_study_detail" record.pk %}" target="_blank" title="Voir">{{record.name}}'
actions = tables.TemplateColumn(ACTIONS, verbose_name="Actions", orderable=False)
link_name = tables.TemplateColumn(LINK_NAME, verbose_name="Nom", orderable=True)
class Meta:
model = Study
......@@ -50,7 +52,7 @@ class StudyTable(tables.Table):
attrs = {"class": "table table-striped table-condensed"}
fields = (
"actions",
"name",
"link_name",
"uuid",
"year",
"project_manager",
......
......@@ -73,7 +73,10 @@
<div class="list-group">
{% for s in object.sessions.all %}
<div class="list-group-item">
<h4 class="list-group-item-heading">{{s.place.name}} - {{s.date_start}} ({{s.contact.descr}})
<h4 class="list-group-item-heading">
<span class="pull-right label label-default label-lg">{{s.contact.descr}}</span>
{{s.place.name}} - {{s.date_start}}
</h4>
<p class="list-group-item-text">
<dl class="dl-horizontal">
......@@ -84,7 +87,11 @@
<dd>
<ul class="list-unstyled">
{%for o in s.sighting_set.all %}
<li>{{o.codesp.sci_name}} : {{o.total_count}} {{o.is_breeding}}</li>
<li class="{{o.codesp.sp_true|yesno:',text-muted'}}">
<strong>{{o.codesp.common_name_fr}}</strong>
(<i>{{o.codesp.sci_name}}</i>)
: {{o.total_count}}
{{o.is_breeding}}</li>
{% endfor %}
</ul>
</dd>
......
......@@ -20,7 +20,7 @@ urlpatterns = [
path("study/<int:pk>/delete", StudyDelete.as_view(), name="study_delete"),
path("study/list", StudyList.as_view(), name="study_list"),
path("study/list/<int:page>", StudyList.as_view(), name="url_study_list"),
path("study/detail/<int:pk>", StudyDetail.as_view(), name="url_study_detail"),
path("study/<int:pk>/detail", StudyDetail.as_view(), name="url_study_detail"),
path(
"api/v1/study/<int:pk>/observations", StudySightings.as_view(), name="api_study_sightings"
),
......
......@@ -67,15 +67,17 @@ class PlaceViewAuthMixin(LoginRequiredMixin):
def dispatch(self, request, *args, **kwargs):
self.obj = self.get_object()
if not request.user.is_authenticated:
logger.debug(f"request.user {request.user} {dir(request.user)}")
logger.debug(f"self {self} {dir(self)}")
user = request.user
if not user.is_authenticated:
return redirect(f"{reverse('auth_login')}?next={request.path}")
else:
loggeduser = get_user_model().objects.get(id=request.user.id)
if (
loggeduser.edit_all_data
or self.obj.created_by == loggeduser
or (self.obj.is_hidden and loggeduser in self.obj.authorized_user.all())
or (loggeduser.is_resp and self.obj.territory in loggeduser.resp_territory.all())
user.edit_all_data
or self.obj.created_by == user
or (self.obj.is_hidden and user in self.obj.authorized_user.all())
or (user.is_resp and self.obj.territory in user.resp_territory.all())
or not self.obj.is_hidden
):
logger.debug("access granted")
......@@ -450,13 +452,7 @@ class PlaceFilteringMixin:
"""Mixin used for Place lists filtering"""
def get_queryset(self, *args, **kwargs):
qs = (
Place.objects.select_related("municipality")
.select_related("metaplace")
.select_related("creator")
.select_related("type")
.select_related("territory")
)
qs = super(PlaceFilteringMixin, self).get_queryset()
municipality = self.request.query_params.get("municipality", None)
name = self.request.query_params.get("name", None)
......@@ -470,8 +466,6 @@ class PlaceFilteringMixin:
managed = self.request.query_params.get("managed", None)
territory = self.request.query_params.get("territory", None)
qs = Place.objects.order_by("-timestamp_create")
qs = (
qs.filter(
Q(municipality__name__icontains=municipality)
......@@ -519,9 +513,6 @@ class PlaceFilteringMixin:
)
qs = qs.filter(territory__id=territory) if territory is not None else qs
qs = qs.select_related("created_by", "type").prefetch_related(
Prefetch("municipality", queryset=Municipality.objects.only("name", "code"))
)
logger.debug(f"Q {qs}")
return qs
......@@ -530,9 +521,7 @@ class PlaceListPermissionsMixin:
def get_queryset(self, *args, **kwargs):
qs = super(PlaceListPermissionsMixin, self).get_queryset()
logged_user = self.request.user
user = get_user_model().objects.get(id=logged_user.id)
user = self.request.user
if user.access_all_data or user.edit_all_data or user.is_superuser:
qs = qs
......@@ -550,7 +539,6 @@ class PlaceListPermissionsMixin:
.distinct()
.order_by("-timestamp_update")
)
logger.debug(qs.query)
return qs
......
......@@ -1016,7 +1016,7 @@ class Sighting(models.Model):
)
def __str__(self):
return "%s ∙ %s ∙ %s" % (self.session, self.codesp, self.total_count)
return "%s ∙ %s" % (self.codesp, self.total_count)
def get_absolute_url(self):
return reverse("sights:sighting_detail", kwargs={"pk": self.id_sighting})
......
......@@ -28,20 +28,28 @@ class GeoJSONSighting(
SightingFilteredListWithPermissions,
ListAPIView,
):
queryset = Sighting.objects.all()
queryset = (
Sighting.objects.select_related("created_by")
.select_related("updated_by")
.select_related("observer")
.select_related("session")
.select_related("codesp")
.prefetch_related("session__place")
.prefetch_related("session__place__municipality")
.prefetch_related("session__place__territory")
.prefetch_related("session__place__type")
.prefetch_related("session__contact")
.prefetch_related("session__main_observer")
.prefetch_related("session__other_observer")
.prefetch_related("session__study")
.all()
)
serializer_class = GeoSightingSerializer
pagination_class = LargeGeoJsonPageNumberPagination
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset()
qs = qs.select_related("created_by", "codesp", "session",).prefetch_related(
Prefetch("session__contact"),
Prefetch("session__place"),
Prefetch("session__place__municipality"),
Prefetch("session__place__territory"),
)
return qs.distinct().order_by("-timestamp_update")
......@@ -56,6 +64,7 @@ class EditSighting(SightingListPermissionsMixin, ModelViewSet):
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset()
qs = qs.select_related("countdetails")
if self.action in ("list", "create"):
session = self.kwargs["session"]
qs = qs.filter(session=session)
......
......@@ -99,7 +99,6 @@ class GeoSightingSerializer(serializers.GeoFeatureModelSerializer):
)
def get_geom(self, sighting):
logger.debug(f"sighting {sighting}")
return sighting.session.place.geom
......
......@@ -6,7 +6,7 @@ from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import IntegrityError
from django.db.models import Q
from django.db.models import Prefetch, Q
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
......@@ -232,10 +232,23 @@ class SightingMyList(LoginRequiredMixin, ExportMixin, SingleTableView):
)
.distinct()
.order_by("-timestamp_update")
.select_related("created_by")
.select_related("updated_by")
.select_related("observer")
.select_related("session")
.select_related("codesp")
.prefetch_related("session__place")
.prefetch_related("session__place__municipality")
.prefetch_related("session__place__territory")
.prefetch_related("session__place__type")
.prefetch_related("session__contact")
.prefetch_related("session__main_observer")
.prefetch_related("session__other_observer")
.prefetch_related("session__study")
)
class SightingUserList(AdvancedUserViewMixin, SingleTableView):
class SightingUserList(AdvancedUserViewMixin, ExportMixin, SingleTableView):
table_class = SightingTable
template_name = "table.html"
table_pagination = {"per_page": 25}
......@@ -271,10 +284,25 @@ class SightingUserList(AdvancedUserViewMixin, SingleTableView):
def get_queryset(self):
user = get_user_model().objects.get(id=self.kwargs.get("pk"))
loggeduser = get_user_model().objects.get(id=self.request.user.id)
userlist = Sighting.objects.filter(
Q(created_by=user)
| Q(observer=user)
| Q(session__other_observer__username__contains=user.username)
userlist = (
Sighting.objects.filter(
Q(created_by=user)
| Q(observer=user)
| Q(session__other_observer__username__contains=user.username)
)
.select_related("created_by")
.select_related("updated_by")
.select_related("observer")
.select_related("session")
.select_related("codesp")
.prefetch_related("session__place")
.prefetch_related("session__place__municipality")
.prefetch_related("session__place__territory")
.prefetch_related("session__place__type")
.prefetch_related("session__contact")
.prefetch_related("session__main_observer")
.prefetch_related("session__other_observer")
.prefetch_related("session__study")
)
if loggeduser.access_all_data or loggeduser.edit_all_data:
return userlist.distinct().order_by("-timestamp_update")
......
......@@ -6,11 +6,24 @@ from rest_framework import generics
from rest_framework_gis.filters import InBBoxFilter
from sights.mixins import LargeGeoJsonPageNumberPagination, PlaceFilteredListWithPermissions
from sights.models import Place
from sights.place.serializers import CountModelMixin, PlaceSerializer
# Get an instance of a logger
logger = logging.getLogger(__name__)
place_qs = (
(
Place.objects.select_related("municipality")
.select_related("metaplace")
.select_related("created_by")
.select_related("type")
.select_related("territory")
)
.order_by("-timestamp_update")
.all()
)
class GeoJSONPlaces(
LoginRequiredMixin,
......@@ -20,10 +33,7 @@ class GeoJSONPlaces(
):
serializer_class = PlaceSerializer
pagination_class = LargeGeoJsonPageNumberPagination
def get_queryset(self):
qs = super(GeoJSONPlaces, self).get_queryset()
return qs.order_by("-timestamp_update").all()
queryset = place_qs
class GeoJSONBBoxFilteredPlaces(
......@@ -36,8 +46,4 @@ class GeoJSONBBoxFilteredPlaces(
bbox_filter_field = "geom"
filter_backends = (InBBoxFilter,)
bbox_filter_include_overlapping = True # Optional
def get_queryset(self):
qs = super(GeoJSONBBoxFilteredPlaces, self).get_queryset()
return qs.order_by("-timestamp_update").all()
queryset = place_qs
......@@ -146,10 +146,22 @@ class PlaceDetail(PlaceViewAuthMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(PlaceDetail, self).get_context_data(**kwargs)
loggeduser = self.request.user
logger.debug(loggeduser)
logger.debug(f"loggeduser {loggeduser} f{dir(loggeduser)}")
pk = self.kwargs.get("pk")
placeobject = Place.objects.get(id_place=pk)
placeobject = (
Place.objects.select_related("territory")
.prefetch_related("authorized_user")
.select_related("created_by")
).get(id_place=pk)
sessions = (
Session.objects.filter(place=pk)
.select_related("main_observer")
.select_related("contact")
.select_related("created_by")
.prefetch_related("other_observer")
.prefetch_related("sighting_set")
.prefetch_related("sighting_set__codesp")
)
# placeneighbour = Place.objects.filter(geom__within=(placeobject.geom, D(m=50)))
placedetail = False
#  Etats des lieux à afficher en fonction du type de localité (tree, build, bridge, cave)
......@@ -167,16 +179,11 @@ class PlaceDetail(PlaceViewAuthMixin, DetailView):
f"{{% url 'sights:{placeobject.type.category}_create' pk=object.pk %}}"
)
# Données de session disponibles pour l'observateur
if (get_user_model().objects.filter(id=loggeduser.id).filter(access_all_data=True)) or (
get_user_model().objects.filter(id=loggeduser.id).filter(is_resp=True)
and placeobject.territory
in get_user_model().objects.get(id=loggeduser.id).resp_territory.all()
if loggeduser.access_all_data or (
loggeduser.is_resp and placeobject.territory in loggeduser.resp_territory.all()
):
# Si l'utilisateur a un accès total à toute la base
sessioncount = Session.objects.filter(place=pk).count()
sessiontable = PlaceSessionTable(
Session.objects.filter(place=pk).order_by("-date_start")
)
sessions = sessions
elif placeobject in Place.objects.filter(
(Q(is_hidden=True) & Q(authorized_user__username__contains=loggeduser.username))
| (Q(is_hidden=True) & Q(created_by=loggeduser))
......@@ -185,23 +192,10 @@ class PlaceDetail(PlaceViewAuthMixin, DetailView):
# liste des sessions correspond aux sessions ou l'utilisateur est créateur ou observateur
# TODO Gérer la cohérence entre les restrictions de vue de la localité et celles de la table des sessions
# Filtrage, l'utilisateur doit être créateur ou l'un des observateurs de la session
sessioncount = Session.objects.filter(
Q(place=pk)
& (
Q(created_by=loggeduser)
| Q(main_observer=loggeduser)
| Q(other_observer=loggeduser)
)
).count()
sessiontable = PlaceSessionTable(
Session.objects.filter(
Q(place=pk)
& (
Q(created_by=loggeduser)
| Q(main_observer=loggeduser)
| Q(other_observer=loggeduser)
)
)
sessions = sessions.filter(
Q(created_by=loggeduser)
| Q(main_observer=loggeduser)
| Q(other_observer=loggeduser)
)
else:
# Si l'utilisateur est un simple observateur et que le site est non sensible, il peut en voir le détail et la
......@@ -212,28 +206,16 @@ class PlaceDetail(PlaceViewAuthMixin, DetailView):
% (datetime.datetime.now().strftime("%Y%m%d %I%M%S"), loggeduser, pk)
)
if SEE_ALL_NON_SENSITIVE_DATA:
sessionDatas = Session.objects.filter(Q(place=pk) & Q(is_confidential=False))
sessioncount = sessionDatas.count()
sessiontable = PlaceSessionTable(sessionDatas)
sessions = sessions.filter(is_confidential=False)
else:
sessioncount = Session.objects.filter(
Q(place=pk)
& (
Q(created_by=loggeduser)
| Q(main_observer=loggeduser)
| Q(other_observer=loggeduser)
)
).count()
sessiontable = PlaceSessionTable(
Session.objects.filter(
Q(place=pk)
& (
Q(created_by=loggeduser)
| Q(main_observer=loggeduser)
| Q(other_observer=loggeduser)
)
)
sessions = sessions.filter(
Q(created_by=loggeduser)
| Q(main_observer=loggeduser)
| Q(other_observer=loggeduser)
)
sessioncount = sessions.count()
sessiontable = PlaceSessionTable(sessions.order_by("-date_start"))
context["icon"] = "fa fa-fw fa-map"
context["title"] = _("Détail d'une localité")
context[
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment