Skip to content

Commit

Permalink
Merge pull request #3965 from GeotrekCE/impr_touristic_event_organizers
Browse files Browse the repository at this point in the history
Touristic event organizers in many to many #3587
  • Loading branch information
submarcos authored Feb 29, 2024
2 parents 1382955 + d133c9a commit 1631b76
Show file tree
Hide file tree
Showing 22 changed files with 148 additions and 51 deletions.
6 changes: 5 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ CHANGELOG

2.102.1+dev (XXXX-XX-XX)
------------------------
- Add popup button to add organizer in touristic event form

**New features**

- Add `include_externals` filter to Cirkwi trek exports, to allow excluding treks with an external id (eid) (#3947)

**Improvments**

- Add popup button to add organizer in touristic event form
- Change the `organizer` field of `TouristicEvent` model to a many to many field named `organizers` (#3587)


2.102.1 (2024-02-20)
--------------------
Expand Down
6 changes: 3 additions & 3 deletions docs/install/advanced-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1389,7 +1389,7 @@ A (nearly?) exhaustive list of attributes available for display and export as co
"email",
"website",
"end_date",
"organizer",
"organizers",
"speaker",
"type",
"accessibility",
Expand Down Expand Up @@ -1929,7 +1929,7 @@ A (nearly?) exhaustive list of attributes available for display and export as co
"contact",
"email",
"website",
"organizer",
"organizers",
"speaker",
"accessibility",
"capacity",
Expand Down Expand Up @@ -2277,7 +2277,7 @@ An exhaustive list of form fields hideable in each module.
'contact',
'email',
'website',
'organizer',
'organizers',
'speaker',
'type',
'accessibility',
Expand Down
11 changes: 10 additions & 1 deletion geotrek/api/mobile/serializers/tourism.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class Meta:
class TouristicEventListSerializer(geo_serializers.GeoFeatureModelSerializer):
geometry = geo_serializers.GeometryField(read_only=True, precision=7, source='geom2d_transformed')
pictures = rest_serializers.SerializerMethodField()
organizers = rest_serializers.SerializerMethodField()
organizer = rest_serializers.SerializerMethodField()

def get_pictures(self, obj):
if not obj.resized_pictures:
Expand All @@ -50,14 +52,21 @@ def get_pictures(self, obj):
'url': os.path.join('/', str(self.context['root_pk']), settings.MEDIA_URL[1:], thdetail_first.name),
}]

def get_organizers(self, obj):
return ", ".join(
map(lambda org: org.label, obj.organizers.all())
)
# for retrocompatibility of API
get_organizer = get_organizers

class Meta:
model = tourism_models.TouristicEvent
id_field = 'pk'
geo_field = 'geometry'
fields = ('id', 'pk', 'name', 'description_teaser', 'description', 'themes', 'pictures',
'begin_date', 'end_date', 'duration', 'meeting_point',
'start_time', 'contact', 'email', 'website',
'organizer', 'speaker', 'type', 'accessibility',
'organizers', 'organizer', 'speaker', 'type', 'accessibility',
'capacity', 'booking', 'target_audience',
'practical_info', 'approved', 'geometry')

Expand Down
2 changes: 1 addition & 1 deletion geotrek/api/tests/test_mobile/test_api_mobile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

TOURISTIC_EVENT_LIST_PROPERTIES_GEOJSON_STRUCTURE = sorted([
'id', 'name', 'description_teaser', 'description', 'themes', 'pictures', 'begin_date', 'end_date', 'duration',
'start_time', 'contact', 'email', 'website', 'organizer', 'speaker', 'type', 'accessibility', 'meeting_point',
'start_time', 'contact', 'email', 'website', 'organizer', 'organizers', 'speaker', 'type', 'accessibility', 'meeting_point',
'capacity', 'booking', 'target_audience', 'practical_info', 'approved',
])

Expand Down
4 changes: 2 additions & 2 deletions geotrek/api/tests/test_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
TOURISTIC_EVENT_DETAIL_JSON_STRUCTURE = sorted([
'id', 'accessibility', 'approved', 'attachments', 'begin_date', 'bookable', 'booking', 'cities', 'contact', 'create_datetime',
'description', 'description_teaser', 'districts', 'duration', 'email', 'end_date', 'external_id', 'geometry',
'meeting_point', 'start_time', 'meeting_time', 'end_time', 'name', 'organizer', 'organizer_id', 'capacity', 'pdf', 'place', 'portal',
'meeting_point', 'start_time', 'meeting_time', 'end_time', 'name', 'organizer', 'organizers', 'organizers_id', 'capacity', 'pdf', 'place', 'portal',
'practical_info', 'provider', 'published', 'source', 'speaker', 'structure', 'target_audience', 'themes',
'type', 'update_datetime', 'url', 'uuid', 'website', 'cancelled', 'cancellation_reason', 'participant_number'
])
Expand Down Expand Up @@ -3241,8 +3241,8 @@ def setUpTestData(cls):
capacity=12,
bookable=False,
place=cls.place,
organizer=cls.organizer
)
cls.touristic_event5.organizers.set([cls.organizer])
cls.touristic_content = tourism_factory.TouristicContentFactory(geom=Point(0.77802, 43.047482, srid=4326))

def test_touristic_event_list(self):
Expand Down
1 change: 1 addition & 0 deletions geotrek/api/v2/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ class TouristicEventFilterSet(filters.FilterSet):
widget=CSVWidget(),
queryset=TouristicEventOrganizer.objects.all(),
help_text=_("Filter by one or more organizer, comma-separated."),
field_name="organizers"
)

help_texts = {
Expand Down
28 changes: 20 additions & 8 deletions geotrek/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ class Meta:
)


class TouristicOrganismSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = tourism_models.TouristicEventOrganizer
fields = (
'id',
)


class OrganismSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
name = serializers.CharField(source='organism')

Expand Down Expand Up @@ -503,13 +511,9 @@ def get_departure_city(self, obj):
return city.code if city else None

class TouristicEventSerializer(TouristicModelSerializer):
organizer = serializers.SlugRelatedField(
read_only=True,
slug_field='label'
)
organizer_id = serializers.PrimaryKeyRelatedField(
read_only=True
)
organizers = serializers.SerializerMethodField()
organizer = serializers.SerializerMethodField()
organizers_id = serializers.PrimaryKeyRelatedField(many=True, source='organizers', read_only=True)
attachments = AttachmentSerializer(many=True, source='sorted_attachments')
url = HyperlinkedIdentityField(view_name='apiv2:touristicevent-detail')
begin_date = serializers.DateField()
Expand Down Expand Up @@ -545,14 +549,22 @@ def get_participant_number(self, obj):
def get_end_date(self, obj):
return obj.end_date or obj.begin_date

def get_organizers(self, obj):
return ", ".join(
map(lambda org: org.label, obj.organizers.all())
)

# for retrocompatibility of API
get_organizer = get_organizers

class Meta(TimeStampedSerializer.Meta):
model = tourism_models.TouristicEvent
fields = TimeStampedSerializer.Meta.fields + (
'id', 'accessibility', 'approved', 'attachments', 'begin_date', 'bookable',
'booking', 'cancellation_reason', 'cancelled', 'capacity', 'cities',
'contact', 'description', 'description_teaser', 'districts', 'duration',
'email', 'end_date', 'end_time', 'external_id', 'geometry', 'meeting_point',
'meeting_time', 'name', 'organizer', 'organizer_id', 'participant_number', 'pdf', 'place',
'meeting_time', 'name', 'organizers', 'organizer', 'organizers_id', 'participant_number', 'pdf', 'place',
'portal', 'practical_info', 'provider', 'published', 'source', 'speaker',
'start_time', 'structure', 'target_audience', 'themes', 'type',
'url', 'uuid', 'website'
Expand Down
2 changes: 1 addition & 1 deletion geotrek/api/v2/views/tourism.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def get_queryset(self):
activate(self.request.GET.get('language'))
return tourism_models.TouristicEvent.objects.existing()\
.select_related('type') \
.prefetch_related('themes', 'source', 'portal',
.prefetch_related('themes', 'source', 'portal', 'organizers',
Prefetch('attachments',
queryset=Attachment.objects.select_related('license', 'filetype', 'filetype__structure'))
) \
Expand Down
8 changes: 4 additions & 4 deletions geotrek/tourism/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class TouristicEventForm(CommonForm):
'contact',
'email',
'website',
'organizer',
'organizers',
'speaker',
'accessibility',
'bookable',
Expand Down Expand Up @@ -131,7 +131,7 @@ class TouristicEventForm(CommonForm):
class Meta:
fields = ['name', 'place', 'review', 'published', 'description_teaser', 'description',
'themes', 'begin_date', 'end_date', 'duration', 'meeting_point',
'start_time', 'end_time', 'contact', 'email', 'website', 'organizer', 'speaker',
'start_time', 'end_time', 'contact', 'email', 'website', 'organizers', 'speaker',
'type', 'accessibility', 'capacity', 'booking', 'target_audience',
'practical_info', 'approved', 'source', 'portal', 'geom', 'eid', 'structure', 'bookable',
'cancelled', 'cancellation_reason', 'preparation_duration', 'intervention_duration']
Expand All @@ -145,8 +145,8 @@ def __init__(self, *args, **kwargs):
self.fields['start_time'].widget.attrs['placeholder'] = _('HH:MM')
self.fields['end_time'].widget.attrs['placeholder'] = _('HH:MM')
if self.user.has_perm("tourism.add_touristiceventorganizer"):
self.fields['organizer'].widget = SelectMultipleWithPop(
choices=self.fields['organizer'].choices,
self.fields['organizers'].widget = SelectMultipleWithPop(
choices=self.fields['organizers'].choices,
add_url=TouristicEventOrganizer.get_add_url()
)
# Since we use chosen() in trek_form.html, we don't need the default help text
Expand Down
4 changes: 2 additions & 2 deletions geotrek/tourism/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ msgid "Start date is after end date"
msgstr ""

msgid "Label"
msgstr "Label"
msgstr ""

msgid "Information desk type"
msgstr ""
Expand Down Expand Up @@ -280,7 +280,7 @@ msgid "Event places"
msgstr ""

msgid "Organizer"
msgstr "Organizer"
msgstr ""

msgid "Organizers"
msgstr ""
Expand Down
34 changes: 34 additions & 0 deletions geotrek/tourism/migrations/0050_auto_20231220_0952.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 3.2.23 on 2023-12-20 09:52

from django.db import migrations, models


def forward_func(apps, schema_editors):
ToursticEvent = apps.get_model("tourism", "TouristicEvent")
for event in ToursticEvent.objects.all():
if event.organizer:
event.organizers.add(event.organizer)


def reverse_func(apps, schema_editors):
pass


class Migration(migrations.Migration):

dependencies = [
('tourism', '0049_alter_touristiccontentcategory_color'),
]

operations = [
migrations.AddField(
model_name='touristicevent',
name='organizers',
field=models.ManyToManyField(blank=True, related_name='touristicevent', to='tourism.TouristicEventOrganizer', verbose_name='Organizers'),
),
migrations.RunPython(forward_func, reverse_func),
migrations.RemoveField(
model_name='touristicevent',
name='organizer',
),
]
14 changes: 14 additions & 0 deletions geotrek/tourism/migrations/0051_merge_20240229_1225.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 3.2.24 on 2024-02-29 11:25

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('tourism', '0050_alter_touristiceventorganizer_label'),
('tourism', '0050_auto_20231220_0952'),
]

operations = [
]
5 changes: 3 additions & 2 deletions geotrek/tourism/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,9 @@ class TouristicEvent(ZoningPropertiesMixin, AddPropertyMixin, PublishableMixin,
blank=True, null=True)
website = models.URLField(verbose_name=_("Website"), max_length=256,
blank=True, null=True)
organizer = models.ForeignKey('tourism.TouristicEventOrganizer', verbose_name=_("Organizer"), blank=True, null=True,
on_delete=models.PROTECT, related_name="touristicevent")

organizers = models.ManyToManyField('tourism.TouristicEventOrganizer', verbose_name=_("Organizers"), blank=True,
related_name="touristicevent")
speaker = models.CharField(verbose_name=_("Speaker"), max_length=256, blank=True)
type = models.ForeignKey(TouristicEventType, verbose_name=_("Type"), blank=True, null=True, on_delete=models.PROTECT)
accessibility = models.TextField(verbose_name=_("Accessibility"), blank=True)
Expand Down
21 changes: 12 additions & 9 deletions geotrek/tourism/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ class TouristicEventApidaeParser(AttachmentApidaeParserMixin, ApidaeParser):
),
'email': 'informations.moyensCommunication',
'website': 'informations.moyensCommunication',
'organizer': 'informations.structureGestion.nom.libelleFr',
'type': 'informationsFeteEtManifestation.typesManifestation.0.libelleFr',
'capacity': 'informationsFeteEtManifestation.nbParticipantsAttendu',
'practical_info': (
Expand Down Expand Up @@ -284,15 +283,17 @@ class TouristicEventApidaeParser(AttachmentApidaeParserMixin, ApidaeParser):
'illustrations'
]
m2m_fields = {
'themes': 'informationsFeteEtManifestation.themes.*.libelleFr'
'themes': 'informationsFeteEtManifestation.themes.*.libelleFr',
'organizers': ('informations.structureGestion.nom.libelleFr',),
}
natural_keys = {
'themes': 'label',
'type': 'type',
'organizer': 'label',
'organizers': 'label',
'source': 'name',
'portal': 'name',
}
# separator = ","

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand All @@ -301,7 +302,7 @@ def __init__(self, *args, **kwargs):
self.field_options = self.field_options.copy()
self.field_options['themes'] = {'create': True}
self.field_options['type'] = {'create': True}
self.field_options['organizer'] = {'create': True}
self.field_options['organizers'] = {'create': True}
if self.type is not None:
self.constant_fields['type'] = self.type
if self.themes is not None:
Expand Down Expand Up @@ -939,16 +940,17 @@ class LEITouristicEventParser(LEIParser):
'ADRPROD_TEL', 'ADRPROD_TEL2', 'ADRPREST_TEL', 'ADRPREST_TEL2'),
'email': ('ADRPROD_EMAIL', 'ADRPREST_EMAIL', 'ADRPREST_EMAIL2'),
'website': ('ADRPROD_URL', 'ADRPREST_URL'),
'organizer': ('RAISONSOC_PERSONNE_EN_CHARGE', 'RAISONSOC_RESPONSABLE'),
'speaker': ('CIVILITE_RESPONSABLE', 'NOM_RESPONSABLE', 'PRENOM_RESPONSABLE'),
'type': 'TYPE_NOM',
'geom': ('LATITUDE', 'LONGITUDE'),
}
m2m_fields = {}
m2m_fields = {
'organizers': ('RAISONSOC_PERSONNE_EN_CHARGE', 'RAISONSOC_RESPONSABLE')
}
type = None
natural_keys = {
'category': 'label',
'organizer': 'label',
'organizers': 'label',
'geom': {'required': True},
'type': 'type',
}
Expand All @@ -958,6 +960,7 @@ class LEITouristicEventParser(LEIParser):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.field_options['organizers'] = {'create': True}
if self.type:
self.constant_fields['type'] = self.type

Expand All @@ -966,9 +969,9 @@ def filter_description_teaser(self, src, val):
val = val.replace('\n', '<br>')
return val

def filter_organizer(self, src, val):
def filter_organizers(self, src, val):
(first, second) = val
return self.apply_filter('organizer', src, [first if first else second])
return self.apply_filter('organizers', src, [first if first else second])

def filter_speaker(self, src, val):
(civilite, nom, prenom) = val
Expand Down
8 changes: 7 additions & 1 deletion geotrek/tourism/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class TouristicEventAPISerializer(PicturesSerializerMixin, PublishableSerializer
source = RecordSourceSerializer(many=True)
portal = TargetPortalSerializer(many=True)
structure = StructureSerializer()
organizers = rest_serializers.SerializerMethodField(source="organizers")

# Nearby
touristic_contents = CloseTouristicContentSerializer(many=True, source='published_touristic_contents')
Expand All @@ -193,6 +194,11 @@ class TouristicEventAPISerializer(PicturesSerializerMixin, PublishableSerializer
type1 = TouristicEventTypeSerializer(many=True)
category = rest_serializers.SerializerMethodField()

def get_organizers(self, obj):
return ", ".join(
map(lambda org: org.label, obj.organizers.all())
)

def __init__(self, instance=None, *args, **kwargs):
super().__init__(instance, *args, **kwargs)
if 'geotrek.diving' in settings.INSTALLED_APPS:
Expand All @@ -205,7 +211,7 @@ class Meta:
fields = (
'id', 'accessibility', 'approved', 'begin_date', 'booking',
'capacity', 'category', 'contact', 'description', 'description_teaser',
'duration', 'email', 'end_date', 'end_time', 'meeting_point', 'organizer',
'duration', 'email', 'end_date', 'end_time', 'meeting_point', 'organizers',
'pois', 'portal', 'practical_info', 'source', 'speaker', 'start_time',
'structure', 'target_audience', 'themes', 'touristic_contents', 'touristic_events',
'treks', 'type', 'type1', 'website'
Expand Down
Loading

0 comments on commit 1631b76

Please sign in to comment.