Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💫 [IMPR] Add participant counts to Touristic Events exports #4471

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions geotrek/common/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@

logger = logging.getLogger(__name__)

ANNOTATION_FORBIDDEN_CHARS = re.compile(r"['`\"\]\[;\s]|--|/\*|\*/")
REPLACEMENT_CHAR = "_"

def normalize_annotation_column_name(col_name):
return ANNOTATION_FORBIDDEN_CHARS.sub(repl=REPLACEMENT_CHAR, string=col_name)

def handler404(request, exception, template_name="404.html"):
if "api/v2" in request.get_full_path():
Expand Down
11 changes: 2 additions & 9 deletions geotrek/maintenance/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from geotrek.authent.decorators import same_structure_required
from geotrek.common.mixins.forms import FormsetMixin
from geotrek.common.mixins.views import CustomColumnsMixin
from geotrek.common.views import normalize_annotation_column_name
from geotrek.common.viewsets import GeotrekMapentityViewSet
from geotrek.feedback.models import Report
from .filters import InterventionFilterSet, ProjectFilterSet
Expand All @@ -24,14 +25,6 @@
logger = logging.getLogger(__name__)


ANNOTATION_FORBIDDEN_CHARS = re.compile(r"['`\"\]\[;\s]|--|/\*|\*/")
REPLACEMENT_CHAR = "_"


def _normalize_annotation_column_name(col_name):
return ANNOTATION_FORBIDDEN_CHARS.sub(repl=REPLACEMENT_CHAR, string=col_name)


class InterventionList(CustomColumnsMixin, MapEntityList):
queryset = Intervention.objects.existing()
mandatory_columns = ['id', 'name']
Expand All @@ -50,7 +43,7 @@ class InterventionFormatList(MapEntityFormat, InterventionList):

@classmethod
def build_cost_column_name(cls, job_name):
return _normalize_annotation_column_name(f"{_('Cost')} {job_name}")
return normalize_annotation_column_name(f"{_('Cost')} {job_name}")

def get_queryset(self):
"""Returns all interventions joined with a new column for each job, to record the total cost of each job in each intervention"""
Expand Down
1 change: 1 addition & 0 deletions geotrek/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,7 @@ def api_bbox(bbox, buffer):
HIDDEN_FORM_FIELDS = {'report': ['assigned_user']}
COLUMNS_LISTS = {}
ENABLE_JOBS_COSTS_DETAILED_EXPORT = False
ENABLE_EVENTS_PARTICIPANTS_DETAILED_EXPORT = False

ACCESSIBILITY_ATTACHMENTS_ENABLED = True

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
</tr>
<tr>
<th>{{ object|verbose:"price" }}</th>
<td>{{ object.price }}</td>
<td>{% if object.price %}{{ object.price }}
{% else %}<span class="none">{% trans "None" %}</span>{% endif %}</td>
</tr>
<tr>
<th>{{ object|verbose:"meeting_point" }}</th>
Expand Down Expand Up @@ -105,15 +106,17 @@
</tr>
<tr>
<th>{{ object|verbose:"capacity" }}</th>
<td>{{ object.capacity }}</td>
<td>{% if object.capacity %}{{ object.capacity }}
{% else %}<span class="none">{% trans "None" %}</span>{% endif %}</td>
</tr>
<tr>
<th>{{ object|verbose:"cancelled" }}</th>
<td>{{ object.cancelled|yesno:_("Yes,No") }}</td>
</tr>
<tr>
<th>{{ object|verbose:"cancellation_reason" }}</th>
<td>{{ object.cancellation_reason|safe }}</td>
<td>{% if object.cancellation_reason %}{{ object.cancellation_reason|safe }}
{% else %}<span class="none">{% trans "None" %}</span>{% endif %}</td>
</tr>
<tr>
<th>{% trans "Source" %}</th>
Expand Down Expand Up @@ -174,11 +177,13 @@
</tr>
<tr>
<th>{{ object|verbose:"preparation_duration" }}</th>
<td>{{ object.preparation_duration }}</td>
<td>{% if object.preparation_duration %}{{ object.preparation_duration|safe }}
{% else %}<span class="none">{% trans "None" %}</span>{% endif %}</td>
</tr>
<tr>
<th>{{ object|verbose:"intervention_duration" }}</th>
<td>{{ object.intervention_duration }}</td>
<td>{% if object.intervention_duration %}{{ object.intervention_duration|safe }}
{% else %}<span class="none">{% trans "None" %}</span>{% endif %}</td>
</tr>

{% include "common/publication_info_fragment.html" %}
Expand Down
56 changes: 50 additions & 6 deletions geotrek/tourism/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from django.contrib.auth.decorators import login_required
from django.contrib.gis.db.models.functions import Transform
from django.core.exceptions import PermissionDenied
from django.db.models import Sum
from django.db.models import Sum, OuterRef, F
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.utils.html import escape
from django.views.generic import CreateView
from mapentity.views import (MapEntityCreate, MapEntityUpdate, MapEntityList, MapEntityDetail, MapEntityFilter,
Expand All @@ -17,13 +18,13 @@
from geotrek.authent.decorators import same_structure_required
from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin
from geotrek.common.models import RecordSource, TargetPortal
from geotrek.common.views import DocumentPublic, DocumentBookletPublic, MarkupPublic
from geotrek.common.views import DocumentPublic, DocumentBookletPublic, MarkupPublic, normalize_annotation_column_name
from geotrek.common.viewsets import GeotrekMapentityViewSet
from geotrek.trekking.models import Trek
from .filters import TouristicContentFilterSet, TouristicEventFilterSet
from .forms import TouristicContentForm, TouristicEventForm, TouristicEventOrganizerFormPopup
from .models import (TouristicContent, TouristicEvent, TouristicContentCategory, TouristicEventOrganizer,
InformationDesk)
InformationDesk, TouristicEventParticipantCategory, TouristicEventParticipantCount)
from .serializers import (TouristicContentSerializer, TouristicEventSerializer,
TrekInformationDeskGeojsonSerializer,
TouristicContentGeojsonSerializer, TouristicEventGeojsonSerializer)
Expand Down Expand Up @@ -184,13 +185,56 @@ class TouristicEventFormatList(MapEntityFormat, TouristicEventList):
'date_insert', 'date_update', 'source', 'portal',
'review', 'published', 'publication_date',
'cities', 'districts', 'areas', 'approved', 'uuid',
'cancelled', 'cancellation_reason', 'total_participants', 'place',
'cancelled', 'cancellation_reason', 'place',
'preparation_duration', 'intervention_duration', 'price'
]

@classmethod
def build_participants_column_name(cls, category_label):
return normalize_annotation_column_name(f"{_('Participants')} {category_label}")

@classmethod
def get_mandatory_columns(cls):
mandatory_columns = ['id']
if settings.ENABLE_EVENTS_PARTICIPANTS_DETAILED_EXPORT:
categories_names = list(TouristicEventParticipantCategory.objects.order_by('order').values_list('label', flat=True))
# Create column names for each unique category
categories_columns_names = list(map(cls.build_participants_column_name, categories_names))
# Add these column names to export
mandatory_columns = mandatory_columns + categories_columns_names + ['total_participants']
else:
mandatory_columns = mandatory_columns + ['total_participants']
return mandatory_columns

def get_queryset(self):
qs = super().get_queryset().select_related('place', 'cancellation_reason').prefetch_related('participants')
return qs.annotate(total_participants=Sum('participants__count'))
"""Returns all events joined with a new column for each participant count"""

queryset = super().get_queryset().select_related('place', 'cancellation_reason').prefetch_related('participants')

if settings.ENABLE_EVENTS_PARTICIPANTS_DETAILED_EXPORT:
# Get all participants categories, as unique ids and names
categories = TouristicEventParticipantCategory.objects.order_by('order').values_list('id', 'label')

# Iter over unique categories
for category_id, label in categories:

# Create column name for current category
column_name = self.build_participants_column_name(label)

# Create subquery to retrieve category count (renamed because of ambiguity with 'count' method)
subquery = (TouristicEventParticipantCount.objects.filter(
event=OuterRef('pk'),
category=category_id
).annotate(
category_count=F('count')
).values('category_count'))

# Annotate queryset with this cost query
params = {column_name: subquery}
queryset = queryset.annotate(**params).annotate(total_participants=Sum('participants__count'))
else:
return queryset.annotate(total_participants=Sum('participants__count'))
return queryset


class TouristicEventDetail(CompletenessMixin, MapEntityDetail):
Expand Down
Loading