diff --git a/docs/changelog.rst b/docs/changelog.rst index c09c82784a..d2227b73c4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,12 +5,17 @@ CHANGELOG 2.101.5+dev (XXXX-XX-XX) ------------------------ +**New features** + +- Add UUIDs of parent and children ``Courses`` and ``Sites`` in APIv2 (#3569) + + 2.101.5 (2024-01-11) -------------------- **New features** --Land: Add ``CirculationEdge`` model to manage circulation types and authorization types in the land module (#3578) +- Land: Add ``CirculationEdge`` model to manage circulation types and authorization types in the land module (#3578) - Generalize``AccessMean`` model and add field ``access`` to ``Intervention`` (#3819) **Improvements** diff --git a/geotrek/api/tests/test_v2.py b/geotrek/api/tests/test_v2.py index 976177bb9e..0871fa0335 100644 --- a/geotrek/api/tests/test_v2.py +++ b/geotrek/api/tests/test_v2.py @@ -158,9 +158,9 @@ RESERVATION_SYSTEM_PROPERTIES_JSON_STRUCTURE = sorted(['name', 'id']) SITE_PROPERTIES_JSON_STRUCTURE = sorted([ - 'accessibility', 'advice', 'ambiance', 'attachments', 'children', 'cities', 'courses', 'description', 'description_teaser', 'districts', 'eid', - 'geometry', 'id', 'information_desks', 'labels', 'managers', 'name', 'orientation', 'parent', 'period', 'portal', - 'practice', 'provider', 'pdf', 'ratings', 'sector', 'source', 'structure', 'themes', 'type', 'url', 'uuid', + 'accessibility', 'advice', 'ambiance', 'attachments', 'children', 'children_uuids', 'cities', 'courses', 'courses_uuids', 'description', + 'description_teaser', 'districts', 'eid', 'geometry', 'id', 'information_desks', 'labels', 'managers', 'name', 'orientation', 'parent', + 'parent_uuid', 'period', 'portal', 'practice', 'provider', 'pdf', 'ratings', 'sector', 'source', 'structure', 'themes', 'type', 'url', 'uuid', 'view_points', 'wind', 'web_links' ]) @@ -186,9 +186,9 @@ COURSE_PROPERTIES_JSON_STRUCTURE = sorted([ 'accessibility', 'advice', 'cities', 'description', 'districts', 'eid', 'equipment', 'geometry', 'height', 'id', - 'length', 'name', 'ratings', 'ratings_description', 'sites', 'structure', - 'type', 'url', 'attachments', 'max_elevation', 'min_elevation', 'parents', 'provider', - 'pdf', 'points_reference', 'children', 'duration', 'gear', 'uuid' + 'length', 'name', 'ratings', 'ratings_description', 'sites', 'sites_uuids', 'structure', + 'type', 'url', 'attachments', 'max_elevation', 'min_elevation', 'parents', 'parents_uuids', 'provider', + 'pdf', 'points_reference', 'children', 'children_uuids', 'duration', 'gear', 'uuid' ]) COURSETYPE_PROPERTIES_JSON_STRUCTURE = sorted(['id', 'name', 'practice']) @@ -4429,6 +4429,11 @@ def test_site_children_published_serializing(self): self.assertIn(self.site_leaf_published.pk, children) self.assertIn(self.site_leaf_published_2.pk, children) self.assertNotIn(self.site_leaf_unpublished.pk, children) + children_uuids = response.json()['children_uuids'] + self.assertEqual(2, len(children_uuids)) + self.assertIn(str(self.site_leaf_published.uuid), children_uuids) + self.assertIn(str(self.site_leaf_published_2.uuid), children_uuids) + self.assertNotIn(str(self.site_leaf_unpublished.uuid), children_uuids) def test_site_parent_unpublished_serializing(self): response = self.get_site_detail(self.site_node_parent_unpublished.pk) @@ -4448,8 +4453,15 @@ def test_site_parent_and_children_serializing_by_lang(self): self.assertIn(self.site_leaf_published_fr.pk, children) self.assertNotIn(self.site_leaf_published_not_fr.pk, children) self.assertNotIn(self.site_leaf_unpublished_fr.pk, children) + children_uuids = site_published_fr['children_uuids'] + self.assertEqual(1, len(children_uuids)) + self.assertIn(str(self.site_leaf_published_fr.uuid), children_uuids) + self.assertNotIn(str(self.site_leaf_published_not_fr.uuid), children_uuids) + self.assertNotIn(str(self.site_leaf_unpublished_fr.uuid), children_uuids) parent = site_published_fr['parent'] + parent_uuid = site_published_fr['parent_uuid'] self.assertEqual(parent, self.site_root_fr.pk) + self.assertEqual(parent_uuid, str(self.site_root_fr.uuid)) class OutdoorFilterByPracticesTestCase(BaseApiTest): @@ -4509,6 +4521,9 @@ def setUpTestData(cls): cls.course = outdoor_factory.CourseFactory() cls.course.parent_sites.set([cls.site.pk]) cls.course2 = outdoor_factory.CourseFactory() + cls.course3 = outdoor_factory.CourseFactory() + outdoor_models.OrderedCourseChild.objects.create(parent=cls.course, child=cls.course2) + outdoor_models.OrderedCourseChild.objects.create(parent=cls.course3, child=cls.course) cls.information_desk = tourism_factory.InformationDeskFactory() cls.site.information_desks.set([cls.information_desk]) @@ -4522,6 +4537,30 @@ def test_filter_courses_by_portal(self): self.assertIn(self.course.pk, all_ids) self.assertNotIn(self.course2.pk, all_ids) + def test_course_serialized_parent_site(self): + response = self.get_course_detail(self.course.pk) + self.assertEqual(response.status_code, 200) + parent_sites_pk = response.json()['sites'] + parent_sites_uuid = response.json()['sites_uuids'] + parent_courses_uuid = response.json()['parents_uuids'] + parent_courses_pk = response.json()['parents'] + children_courses_uuid = response.json()['children_uuids'] + children_courses_pk = response.json()['children'] + self.assertEqual(parent_sites_pk, [self.site.pk]) + self.assertEqual(parent_sites_uuid, [ str(self.site.uuid)]) + self.assertEqual(parent_courses_pk, [{'order': 0, 'course': self.course3.pk}]) + self.assertEqual(parent_courses_uuid, [{'order': 0, 'course': str(self.course3.uuid)}]) + self.assertEqual(children_courses_pk, [{'order': 0, 'course': self.course2.pk}]) + self.assertEqual(children_courses_uuid, [{'order': 0, 'course': str(self.course2.uuid)}]) + + def test_site_serialized_children_course(self): + response = self.get_site_detail(self.site.pk) + self.assertEqual(response.status_code, 200) + child_course_pk = response.json()['courses'] + child_course_uuid = response.json()['courses_uuids'] + self.assertEqual(child_course_pk, [self.course.pk]) + self.assertEqual(child_course_uuid, [str(self.course.uuid)]) + def test_filter_courses_by_themes(self): response = self.get_course_list({'themes': self.theme.pk}) self.assertEqual(response.status_code, 200) diff --git a/geotrek/api/v2/serializers.py b/geotrek/api/v2/serializers.py index 1d52de42bb..70c4af0f1f 100644 --- a/geotrek/api/v2/serializers.py +++ b/geotrek/api/v2/serializers.py @@ -1,6 +1,6 @@ -from bs4 import BeautifulSoup import json +from bs4 import BeautifulSoup from django.conf import settings from django.contrib.gis.db.models.functions import Transform from django.contrib.gis.geos import MultiLineString, Point @@ -13,6 +13,7 @@ from easy_thumbnails.engine import NoSourceGenerator from easy_thumbnails.exceptions import InvalidImageFormatError from easy_thumbnails.files import get_thumbnailer +from modeltranslation.utils import build_localized_fieldname from PIL.Image import DecompressionBombError from rest_framework import serializers from rest_framework.relations import HyperlinkedIdentityField @@ -1144,8 +1145,11 @@ class SiteSerializer(PDFSerializerMixin, DynamicFieldsMixin, serializers.ModelSe attachments = AttachmentSerializer(many=True) sector = serializers.SerializerMethodField() courses = serializers.SerializerMethodField() + courses_uuids = serializers.SerializerMethodField() children = serializers.SerializerMethodField() + children_uuids = serializers.SerializerMethodField() parent = serializers.SerializerMethodField() + parent_uuid = serializers.SerializerMethodField() pdf = serializers.SerializerMethodField('get_pdf_url') cities = serializers.SerializerMethodField() districts = serializers.SerializerMethodField() @@ -1168,33 +1172,59 @@ def get_courses(self, obj): language = request.GET.get('language') for course in obj.children_courses.all(): if language: - if getattr(course, f"published_{language}"): + if getattr(course, build_localized_fieldname('published', language)): courses.append(course.pk) else: if course.published: courses.append(course.pk) return courses + def get_courses_uuids(self, obj): + courses = [] + request = self.context['request'] + language = request.GET.get('language') + for course in obj.children_courses.all(): + if language: + if getattr(course, build_localized_fieldname('published', language)): + courses.append(course.uuid) + else: + if course.published: + courses.append(course.uuid) + return courses + def get_parent(self, obj): parent = None request = self.context['request'] language = request.GET.get('language') if obj.parent: if language: - if getattr(obj.parent, f"published_{language}"): + if getattr(obj.parent, build_localized_fieldname('published', language)): parent = obj.parent.pk else: if obj.parent.published: parent = obj.parent.pk return parent + def get_parent_uuid(self, obj): + parent = None + request = self.context['request'] + language = request.GET.get('language') + if obj.parent: + if language: + if getattr(obj.parent, build_localized_fieldname('published', language)): + parent = obj.parent.uuid + else: + if obj.parent.published: + parent = obj.parent.uuid + return parent + def get_children(self, obj): children = [] request = self.context['request'] language = request.GET.get('language') if language: for site in obj.get_children(): - if getattr(site, f"published_{language}"): + if getattr(site, build_localized_fieldname('published', language)): children.append(site.pk) else: for site in obj.get_children(): @@ -1202,6 +1232,20 @@ def get_children(self, obj): children.append(site.pk) return children + def get_children_uuids(self, obj): + children = [] + request = self.context['request'] + language = request.GET.get('language') + if language: + for site in obj.get_children(): + if getattr(site, build_localized_fieldname('published', language)): + children.append(site.uuid) + else: + for site in obj.get_children(): + if site.published: + children.append(site.uuid) + return children + def get_sector(self, obj): if obj.practice and obj.practice.sector: return obj.practice.sector_id @@ -1210,24 +1254,27 @@ def get_sector(self, obj): class Meta: model = outdoor_models.Site fields = ( - 'id', 'accessibility', 'advice', 'ambiance', 'attachments', 'cities', 'children', 'description', + 'id', 'accessibility', 'advice', 'ambiance', 'attachments', 'cities', 'children', 'children_uuids', 'description', 'description_teaser', 'districts', 'eid', 'geometry', 'information_desks', 'labels', 'managers', - 'name', 'orientation', 'pdf', 'period', 'parent', 'portal', 'practice', 'provider', + 'name', 'orientation', 'pdf', 'period', 'parent', 'parent_uuid', 'portal', 'practice', 'provider', 'ratings', 'sector', 'source', 'structure', 'themes', 'view_points', - 'type', 'url', 'uuid', 'courses', 'web_links', 'wind', + 'type', 'url', 'uuid', 'courses', 'courses_uuids', 'web_links', 'wind', ) class CourseSerializer(PDFSerializerMixin, DynamicFieldsMixin, serializers.ModelSerializer): url = HyperlinkedIdentityField(view_name='apiv2:course-detail') geometry = geo_serializers.GeometryField(read_only=True, source="geom_transformed", precision=7) - children = serializers.ReadOnlyField(source='children_id') - parents = serializers.ReadOnlyField(source='parents_id') + children = serializers.SerializerMethodField() + parents = serializers.SerializerMethodField() + parents_uuids = serializers.SerializerMethodField() + children_uuids = serializers.SerializerMethodField() accessibility = serializers.SerializerMethodField() attachments = AttachmentSerializer(many=True, source='sorted_attachments') equipment = serializers.SerializerMethodField() gear = serializers.SerializerMethodField() ratings_description = serializers.SerializerMethodField() sites = serializers.SerializerMethodField() + sites_uuids = serializers.SerializerMethodField() points_reference = serializers.SerializerMethodField() pdf = serializers.SerializerMethodField('get_pdf_url') cities = serializers.SerializerMethodField() @@ -1257,7 +1304,7 @@ def get_sites(self, obj): language = request.GET.get('language') if language: for site in obj.parent_sites.all(): - if getattr(site, f"published_{language}"): + if getattr(site, build_localized_fieldname('published', language)): sites.append(site.pk) else: for site in obj.parent_sites.all(): @@ -1265,6 +1312,84 @@ def get_sites(self, obj): sites.append(site.pk) return sites + def get_children(self, obj): + courses = [] + request = self.context['request'] + language = request.GET.get('language') + if language: + for ordered_child in obj.course_children.order_by('order'): + course = ordered_child.child + if getattr(course, build_localized_fieldname('published', language)): + courses.append({'order': ordered_child.order, 'course': course.pk}) + else: + for ordered_child in obj.course_children.order_by('order'): + course = ordered_child.child + if getattr(course, "published"): + courses.append({'order': ordered_child.order, 'course': course.pk}) + return courses + + def get_parents(self, obj): + courses = [] + request = self.context['request'] + language = request.GET.get('language') + if language: + for ordered_parent in obj.course_parents.order_by('order'): + course=ordered_parent.parent + if getattr(course, build_localized_fieldname('published', language)): + courses.append({'order': ordered_parent.order, 'course': course.pk}) + else: + for ordered_parent in obj.course_parents.order_by('order'): + course=ordered_parent.parent + if getattr(course, "published"): + courses.append({'order': ordered_parent.order, 'course': course.pk}) + return courses + + def get_children_uuids(self, obj): + courses = [] + request = self.context['request'] + language = request.GET.get('language') + if language: + for ordered_child in obj.course_children.order_by('order'): + course = ordered_child.child + if getattr(course, build_localized_fieldname('published', language)): + courses.append({'order': ordered_child.order, 'course': course.uuid}) + else: + for ordered_child in obj.course_children.order_by('order'): + course = ordered_child.child + if getattr(course, "published"): + courses.append({'order': ordered_child.order, 'course': course.uuid}) + return courses + + def get_parents_uuids(self, obj): + courses = [] + request = self.context['request'] + language = request.GET.get('language') + if language: + for ordered_parent in obj.course_parents.order_by('order'): + course=ordered_parent.parent + if getattr(course, build_localized_fieldname('published', language)): + courses.append({'order': ordered_parent.order, 'course': course.uuid}) + else: + for ordered_parent in obj.course_parents.order_by('order'): + course=ordered_parent.parent + if getattr(course, "published"): + courses.append({'order': ordered_parent.order, 'course': course.uuid}) + return courses + + def get_sites_uuids(self, obj): + sites = [] + request = self.context['request'] + language = request.GET.get('language') + if language: + for site in obj.parent_sites.all(): + if getattr(site, build_localized_fieldname('published', language)): + sites.append(site.uuid) + else: + for site in obj.parent_sites.all(): + if getattr(site, "published"): + sites.append(site.uuid) + return sites + def get_points_reference(self, obj): if not obj.points_reference: return None @@ -1274,10 +1399,10 @@ def get_points_reference(self, obj): class Meta: model = outdoor_models.Course fields = ( - 'id', 'accessibility', 'advice', 'attachments', 'children', 'cities', 'description', 'districts', 'duration', 'eid', + 'id', 'accessibility', 'advice', 'attachments', 'children', 'children_uuids', 'cities', 'description', 'districts', 'duration', 'eid', 'equipment', 'gear', 'geometry', 'height', 'length', 'max_elevation', - 'min_elevation', 'name', 'parents', 'pdf', 'points_reference', 'provider', 'ratings', 'ratings_description', - 'sites', 'structure', 'type', 'url', 'uuid' + 'min_elevation', 'name', 'parents', 'parents_uuids', 'pdf', 'points_reference', 'provider', 'ratings', 'ratings_description', + 'sites', 'sites_uuids', 'structure', 'type', 'url', 'uuid' ) if 'geotrek.feedback' in settings.INSTALLED_APPS: