diff --git a/conf/nginx.conf.in b/conf/nginx.conf.in index 34bafa474b..fcad991901 100644 --- a/conf/nginx.conf.in +++ b/conf/nginx.conf.in @@ -25,7 +25,7 @@ server { access_log /var/log/nginx/geotrek_access.log; error_log /var/log/nginx/geotrek_error.log; - client_max_body_size 10M; + client_max_body_size 200M; location /static { expires 1d; diff --git a/docs/changelog.rst b/docs/changelog.rst index 3633c575c0..2949251fdb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,10 +5,15 @@ CHANGELOG 2.95.0+dev (XXXX-XX-XX) ----------------------- -**Minor improvements** +**New feature** -- APIDAE Trek Parser output now shows APIDAE IDs of entities triggering warnings during import +- Handle very high resolution images (HD Views) that will automatically be tiled, for ``Trek``, ``POI`` and ``Site`` (#3378) +- Handle annotations on HD Views (points, lines, polygons and text) +**Improvements** + +- APIDAE Trek Parser output now shows APIDAE IDs of entities triggering warnings during import +- Update maximum request size in Nginx from 10M to 200M to allow uploading HD pictures (#3378) **Bug fixes** @@ -25,6 +30,10 @@ CHANGELOG - Dependency graph is now checked in CI (see docs/contribute/development to how add a new dependency). - New git pre-commit hook to check all is alright before commit (see docs/contribute/development). +**Warning** + +- The default Nginx configuration template has been improved (https://github.com/GeotrekCE/Geotrek-admin/pull/3298/commits/f9c72d95c1fd7eee2dee26dc73a5927966a812bf) to allow uploading big images. It is highly recommanded to apply changes to your Nginx configuration template (in /opt/geotrek-admin/var/conf/nginx.conf.in). + 2.95.0 (2023-01-24) ----------------------- diff --git a/geotrek/altimetry/locale/de/LC_MESSAGES/django.po b/geotrek/altimetry/locale/de/LC_MESSAGES/django.po index 04ba560e90..682963a726 100644 --- a/geotrek/altimetry/locale/de/LC_MESSAGES/django.po +++ b/geotrek/altimetry/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/altimetry/locale/en/LC_MESSAGES/django.po b/geotrek/altimetry/locale/en/LC_MESSAGES/django.po index 04ba560e90..682963a726 100644 --- a/geotrek/altimetry/locale/en/LC_MESSAGES/django.po +++ b/geotrek/altimetry/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/altimetry/locale/es/LC_MESSAGES/django.po b/geotrek/altimetry/locale/es/LC_MESSAGES/django.po index 04ba560e90..682963a726 100644 --- a/geotrek/altimetry/locale/es/LC_MESSAGES/django.po +++ b/geotrek/altimetry/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/altimetry/locale/fr/LC_MESSAGES/django.po b/geotrek/altimetry/locale/fr/LC_MESSAGES/django.po index 727553098e..fc677764e7 100644 --- a/geotrek/altimetry/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/altimetry/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2015-10-06 12:11+0100\n" "Language-Team: \n" "Language: fr\n" diff --git a/geotrek/altimetry/locale/it/LC_MESSAGES/django.po b/geotrek/altimetry/locale/it/LC_MESSAGES/django.po index 04ba560e90..682963a726 100644 --- a/geotrek/altimetry/locale/it/LC_MESSAGES/django.po +++ b/geotrek/altimetry/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/altimetry/locale/nl/LC_MESSAGES/django.po b/geotrek/altimetry/locale/nl/LC_MESSAGES/django.po index 04ba560e90..682963a726 100644 --- a/geotrek/altimetry/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/altimetry/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/api/locale/de/LC_MESSAGES/django.po b/geotrek/api/locale/de/LC_MESSAGES/django.po index 485910e0e5..590832e74b 100644 --- a/geotrek/api/locale/de/LC_MESSAGES/django.po +++ b/geotrek/api/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -230,7 +230,7 @@ msgid "Trek" msgstr "" msgid "" -"Filter by a trek id. It will show only the sensitive areas related to this " +"Filter by a trek id. It will show only the sensitive areas intersecting this " "trek." msgstr "" @@ -338,13 +338,13 @@ msgstr "" msgid "Filter by one or more category id, comma-separated." msgstr "" -msgid "Filter events on bookable boolean : true/false expected" +msgid "Filter by one or more Place id, comma-separated." msgstr "" -msgid "Filter events on cancelled boolean : true/false expected" +msgid "Filter events on bookable boolean : true/false expected" msgstr "" -msgid "Filter by one or more Place id, comma-separated." +msgid "Filter events on cancelled boolean : true/false expected" msgstr "" msgid "Dates before" diff --git a/geotrek/api/locale/en/LC_MESSAGES/django.po b/geotrek/api/locale/en/LC_MESSAGES/django.po index 485910e0e5..590832e74b 100644 --- a/geotrek/api/locale/en/LC_MESSAGES/django.po +++ b/geotrek/api/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -230,7 +230,7 @@ msgid "Trek" msgstr "" msgid "" -"Filter by a trek id. It will show only the sensitive areas related to this " +"Filter by a trek id. It will show only the sensitive areas intersecting this " "trek." msgstr "" @@ -338,13 +338,13 @@ msgstr "" msgid "Filter by one or more category id, comma-separated." msgstr "" -msgid "Filter events on bookable boolean : true/false expected" +msgid "Filter by one or more Place id, comma-separated." msgstr "" -msgid "Filter events on cancelled boolean : true/false expected" +msgid "Filter events on bookable boolean : true/false expected" msgstr "" -msgid "Filter by one or more Place id, comma-separated." +msgid "Filter events on cancelled boolean : true/false expected" msgstr "" msgid "Dates before" diff --git a/geotrek/api/locale/es/LC_MESSAGES/django.po b/geotrek/api/locale/es/LC_MESSAGES/django.po index 485910e0e5..590832e74b 100644 --- a/geotrek/api/locale/es/LC_MESSAGES/django.po +++ b/geotrek/api/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -230,7 +230,7 @@ msgid "Trek" msgstr "" msgid "" -"Filter by a trek id. It will show only the sensitive areas related to this " +"Filter by a trek id. It will show only the sensitive areas intersecting this " "trek." msgstr "" @@ -338,13 +338,13 @@ msgstr "" msgid "Filter by one or more category id, comma-separated." msgstr "" -msgid "Filter events on bookable boolean : true/false expected" +msgid "Filter by one or more Place id, comma-separated." msgstr "" -msgid "Filter events on cancelled boolean : true/false expected" +msgid "Filter events on bookable boolean : true/false expected" msgstr "" -msgid "Filter by one or more Place id, comma-separated." +msgid "Filter events on cancelled boolean : true/false expected" msgstr "" msgid "Dates before" diff --git a/geotrek/api/locale/fr/LC_MESSAGES/django.po b/geotrek/api/locale/fr/LC_MESSAGES/django.po index 9baf9e0fa5..18a3bcd29e 100644 --- a/geotrek/api/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/api/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-10-20 16:33+0000\n" "Last-Translator: Bastien Potiron \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" @@ -230,7 +230,7 @@ msgid "Trek" msgstr "" msgid "" -"Filter by a trek id. It will show only the sensitive areas related to this " +"Filter by a trek id. It will show only the sensitive areas intersecting this " "trek." msgstr "" @@ -338,13 +338,13 @@ msgstr "" msgid "Filter by one or more category id, comma-separated." msgstr "" -msgid "Filter events on bookable boolean : true/false expected" +msgid "Filter by one or more Place id, comma-separated." msgstr "" -msgid "Filter events on cancelled boolean : true/false expected" +msgid "Filter events on bookable boolean : true/false expected" msgstr "" -msgid "Filter by one or more Place id, comma-separated." +msgid "Filter events on cancelled boolean : true/false expected" msgstr "" msgid "Dates before" diff --git a/geotrek/api/locale/nl/LC_MESSAGES/django.po b/geotrek/api/locale/nl/LC_MESSAGES/django.po index 485910e0e5..590832e74b 100644 --- a/geotrek/api/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/api/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -230,7 +230,7 @@ msgid "Trek" msgstr "" msgid "" -"Filter by a trek id. It will show only the sensitive areas related to this " +"Filter by a trek id. It will show only the sensitive areas intersecting this " "trek." msgstr "" @@ -338,13 +338,13 @@ msgstr "" msgid "Filter by one or more category id, comma-separated." msgstr "" -msgid "Filter events on bookable boolean : true/false expected" +msgid "Filter by one or more Place id, comma-separated." msgstr "" -msgid "Filter events on cancelled boolean : true/false expected" +msgid "Filter events on bookable boolean : true/false expected" msgstr "" -msgid "Filter by one or more Place id, comma-separated." +msgid "Filter events on cancelled boolean : true/false expected" msgstr "" msgid "Dates before" diff --git a/geotrek/api/tests/test_v2.py b/geotrek/api/tests/test_v2.py index d92a08e461..2a399c2f86 100644 --- a/geotrek/api/tests/test_v2.py +++ b/geotrek/api/tests/test_v2.py @@ -1,4 +1,5 @@ import datetime +import json from unittest import skipIf from dateutil.relativedelta import relativedelta @@ -70,7 +71,7 @@ 'next', 'parents', 'parking_location', 'pdf', 'points_reference', 'portal', 'practice', 'previous', 'public_transport', 'provider', 'published', 'ratings', 'ratings_description', 'reservation_system', 'reservation_id', 'route', 'second_external_id', 'source', 'structure', - 'themes', 'update_datetime', 'url', 'uuid', 'web_links' + 'themes', 'update_datetime', 'url', 'uuid', 'view_points', 'web_links' ]) PATH_PROPERTIES_GEOJSON_STRUCTURE = sorted(['comments', 'length_2d', 'length_3d', 'name', 'provider', 'url', 'uuid']) @@ -80,7 +81,7 @@ POI_PROPERTIES_GEOJSON_STRUCTURE = sorted([ 'id', 'create_datetime', 'description', 'external_id', 'name', 'attachments', 'published', 'provider', 'type', 'type_label', 'type_pictogram', - 'update_datetime', 'url', 'uuid' + 'update_datetime', 'url', 'uuid', 'view_points' ]) LABEL_ACCESSIBILITY_DETAIL_JSON_STRUCTURE = sorted([ @@ -149,7 +150,8 @@ SITE_PROPERTIES_JSON_STRUCTURE = sorted([ 'accessibility', 'advice', 'ambiance', 'attachments', 'children', 'cities', 'courses', 'description', 'description_teaser', 'eid', 'geometry', 'id', 'information_desks', 'labels', 'managers', 'name', 'orientation', 'parent', 'period', 'portal', - 'practice', 'provider', 'pdf', 'ratings', 'sector', 'source', 'structure', 'themes', 'type', 'url', 'uuid', 'wind', 'web_links', + 'practice', 'provider', 'pdf', 'ratings', 'sector', 'source', 'structure', 'themes', 'type', 'url', 'uuid', + 'view_points', 'wind', 'web_links' ]) OUTDOORPRACTICE_PROPERTIES_JSON_STRUCTURE = sorted(['id', 'name', 'sector', 'pictogram']) @@ -254,6 +256,11 @@ 'id', 'label', 'structure' ]) +HDVIEWPOINT_DETAIL_JSON_STRUCTURE = sorted([ + 'id', 'annotations', 'author', 'create_datetime', 'geometry', 'legend', + 'license', 'picture_tiles_url', 'poi', 'site', 'title', 'trek', 'thumbnail_url', 'update_datetime', 'uuid' +]) + class BaseApiTest(TestCase): """ Base TestCase for all API profiles """ @@ -438,6 +445,15 @@ def setUpTestData(cls): cls.site2.themes.add(cls.theme3) cls.label_3 = common_factory.LabelFactory() cls.site2.labels.add(cls.label_3) + cls.hdviewpoint_trek = common_factory.HDViewPointFactory( + content_object=cls.treks[0] + ) + cls.hdviewpoint_poi = common_factory.HDViewPointFactory( + content_object=cls.poi + ) + cls.hdviewpoint_site = common_factory.HDViewPointFactory( + content_object=cls.site + ) def check_number_elems_response(self, response, model): json_response = response.json() @@ -767,6 +783,12 @@ def get_sector_list(self, params=None): def get_sector_detail(self, id_sector, params=None): return self.client.get(reverse('apiv2:outdoor-sector-detail', args=(id_sector,)), params) + def get_hdviewpoint_list(self, params=None): + return self.client.get(reverse('apiv2:hdviewpoint-list'), params) + + def get_hdviewpoint_detail(self, id_hdviewpoint, params=None): + return self.client.get(reverse('apiv2:hdviewpoint-detail', args=(id_hdviewpoint,)), params) + class APIAccessAnonymousTestCase(BaseApiTest): """ TestCase for anonymous API profile """ @@ -1319,6 +1341,18 @@ def test_sector_list(self): outdoor_models.Sector ) + def test_hdviewpoint_detail(self): + self.check_structure_response( + self.get_hdviewpoint_detail(self.hdviewpoint_trek.pk), + HDVIEWPOINT_DETAIL_JSON_STRUCTURE + ) + + def test_hdviewpoint_list(self): + self.check_number_elems_response( + self.get_hdviewpoint_list(), + common_models.HDViewPoint + ) + def test_route_detail(self): self.check_structure_response( self.get_route_detail(self.route.pk), @@ -2202,6 +2236,40 @@ def test_sensitivearea_distance_list(self): }) self.assertEqual(response.status_code, 404) + def test_hdviewpoint_detail_content(self): + response = self.get_hdviewpoint_detail(self.hdviewpoint_trek.pk) + self.assertEqual(response.status_code, 200) + json_response = response.json() + self.assertEqual( + json_response.get('picture_tiles_url'), + f"http://testserver/api/hdviewpoint/drf/hdviewpoints/{self.hdviewpoint_trek.pk}/tiles/%7Bz%7D/%7Bx%7D/%7By%7D.png?source=vips" + ) + json.dumps(json_response.get('annotations')) + self.assertIsNone(json_response.get('site')) + self.assertIsNone(json_response.get('poi')) + self.assertEquals(json_response.get('trek').get('uuid'), str(self.treks[0].uuid)) + self.assertEquals(json_response.get('trek').get('id'), self.treks[0].id) + + def test_hdviewpoint_detail_content_poi(self): + response = self.get_hdviewpoint_detail(self.hdviewpoint_poi.pk) + self.assertEqual(response.status_code, 200) + json_response = response.json() + json.dumps(json_response.get('annotations')) + self.assertIsNone(json_response.get('site')) + self.assertIsNone(json_response.get('trek')) + self.assertEquals(json_response.get('poi').get('uuid'), str(self.poi.uuid)) + self.assertEquals(json_response.get('poi').get('id'), self.poi.id) + + def test_hdviewpoint_detail_content_site(self): + response = self.get_hdviewpoint_detail(self.hdviewpoint_site.pk) + self.assertEqual(response.status_code, 200) + json_response = response.json() + json.dumps(json_response.get('annotations')) + self.assertIsNone(json_response.get('poi')) + self.assertIsNone(json_response.get('trek')) + self.assertEquals(json_response.get('site').get('uuid'), str(self.site.uuid)) + self.assertEquals(json_response.get('site').get('id'), self.site.id) + class APIAccessAdministratorTestCase(BaseApiTest): """ TestCase for administrator API profile """ @@ -4216,7 +4284,7 @@ def setUpTestData(cls): @skipIf(not settings.TREKKING_TOPOLOGY_ENABLED, 'Test with dynamic segmentation only') def test_cache_is_used_when_getting_trek_DEM(self): # There are 9 queries to get trek DEM - with self.assertNumQueries(9): + with self.assertNumQueries(10): response = self.client.get(reverse('apiv2:trek-dem', args=(self.trek.pk,))) self.assertEqual(response.status_code, 200) self.assertEqual(response['Content-Type'], 'application/json') @@ -4230,7 +4298,7 @@ def test_cache_is_used_when_getting_trek_DEM(self): def test_cache_is_used_when_getting_trek_DEM_nds(self): trek = trek_factory.TrekFactory.create(geom=LineString((1, 101), (81, 101), (81, 99))) # There are 9 queries to get trek DEM - with self.assertNumQueries(9): + with self.assertNumQueries(10): response = self.client.get(reverse('apiv2:trek-dem', args=(trek.pk,))) self.assertEqual(response.status_code, 200) self.assertEqual(response['Content-Type'], 'application/json') @@ -4242,7 +4310,7 @@ def test_cache_is_used_when_getting_trek_DEM_nds(self): def test_cache_is_used_when_getting_trek_profile(self): # There are 8 queries to get trek profile - with self.assertNumQueries(9): + with self.assertNumQueries(10): response = self.client.get(reverse('apiv2:trek-profile', args=(self.trek.pk,))) self.assertEqual(response.status_code, 200) self.assertEqual(response['Content-Type'], 'application/json') @@ -4256,7 +4324,7 @@ def test_cache_is_used_when_getting_trek_profile(self): def test_cache_is_used_when_getting_trek_profile_svg(self): # There are 8 queries to get trek profile svg - with self.assertNumQueries(9): + with self.assertNumQueries(10): response = self.client.get(reverse('apiv2:trek-profile', args=(self.trek.pk,)), {"format": "svg"}) self.assertEqual(response.status_code, 200) self.assertIn('image/svg+xml', response['Content-Type']) diff --git a/geotrek/api/v2/filters.py b/geotrek/api/v2/filters.py index a4c89961b3..4dce471525 100644 --- a/geotrek/api/v2/filters.py +++ b/geotrek/api/v2/filters.py @@ -961,7 +961,13 @@ class RelatedObjectsPublishedNotDeletedFilter(BaseFilterBackend): def filter_queryset_related_objects_published_not_deleted(self, qs, request, related_name, optional_query=Q()): # Exclude if no related objects exist - qs = qs.exclude(**{'{}'.format(related_name): None}) + # #################################### + # Should be : + # qs = qs.exclude(**{'{}'.format(related_name): None}) + # But we need to bypass this bug : https://code.djangoproject.com/ticket/26261 + # TODO Revert when using Django > 4.2 + qs = qs.filter(**{'{}__isnull'.format(related_name): False}) + # #################################### # Ensure no deleted content is taken in consideration in the filter related_field_name = '{}__deleted'.format(related_name) optional_query &= Q(**{related_field_name: False}) @@ -1011,13 +1017,25 @@ def filter_queryset_related_objects_by_portal(self, request, related_name): def filter_queryset_related_objects_published_not_deleted_by_portal(self, qs, request, related_name): # Exclude if no related objects exist - qs = qs.exclude(**{'{}'.format(related_name): None}) + # #################################### + # Should be : + # qs = qs.exclude(**{'{}'.format(related_name): None}) + # But we need to bypass this bug : https://code.djangoproject.com/ticket/26261 + # TODO Revert when using Django > 4.2 + qs = qs.filter(**{'{}__isnull'.format(related_name): False}) + # #################################### portal_query = self.filter_queryset_related_objects_by_portal(request, related_name) return self.filter_queryset_related_objects_published_not_deleted(qs, request, related_name, portal_query) def filter_queryset_related_objects_published_by_portal(self, qs, request, related_name): # Exclude if no related objects exist - qs = qs.exclude(**{'{}'.format(related_name): None}) + # #################################### + # Should be : + # qs = qs.exclude(**{'{}'.format(related_name): None}) + # But we need to bypass this bug : https://code.djangoproject.com/ticket/26261 + # TODO Revert when using Django > 4.2 + qs = qs.filter(**{'{}__isnull'.format(related_name): False}) + # #################################### portal_query = self.filter_queryset_related_objects_by_portal(request, related_name) return self.filter_queryset_related_objects_published(qs, request, related_name, portal_query) @@ -1079,6 +1097,16 @@ def filter_queryset(self, request, qs, view): return self.filter_queryset_related_objects_published_not_deleted_by_portal(qs, request, 'contents') +class TrekAndSiteAndPOIRelatedPublishedNotDeletedByPortalFilter(RelatedObjectsPublishedNotDeletedByPortalFilter): + def filter_queryset(self, request, qs, view): + set_1 = self.filter_queryset_related_objects_published_not_deleted_by_portal(qs, request, 'trek') + set_2 = self.filter_queryset_related_objects_published_not_deleted_by_portal(qs, request, 'poi') + set_3 = qs.none() + if 'geotrek.outdoor' in settings.INSTALLED_APPS: + set_3 = self.filter_queryset_related_objects_published_by_portal(qs, request, 'site') + return (set_1 | set_2 | set_3).distinct() + + class TreksAndSitesAndTourismRelatedPortalThemeFilter(RelatedObjectsPublishedNotDeletedByPortalFilter): def filter_queryset(self, request, qs, view): set_1 = self.filter_queryset_related_objects_published_not_deleted_by_portal(qs, request, 'treks') diff --git a/geotrek/api/v2/serializers.py b/geotrek/api/v2/serializers.py index e2c0f1728f..976de63fee 100644 --- a/geotrek/api/v2/serializers.py +++ b/geotrek/api/v2/serializers.py @@ -62,6 +62,14 @@ class Meta: auto_bbox = True +class TimeStampedSerializer(serializers.ModelSerializer): + create_datetime = serializers.DateTimeField(source='date_insert') + update_datetime = serializers.DateTimeField(source='date_update') + + class Meta: + fields = ('create_datetime', 'update_datetime') + + def override_serializer(format_output, base_serializer_class): """ Override Serializer switch output format and dimension data @@ -334,6 +342,50 @@ class Meta: fields = ('id', 'advice', 'filter', 'name', 'pictogram') +class HDViewPointSerializer(TimeStampedSerializer): + geometry = geo_serializers.GeometryField(read_only=True, source="geom_transformed", precision=7) + picture_tiles_url = serializers.SerializerMethodField() + thumbnail_url = serializers.SerializerMethodField() + trek = serializers.SerializerMethodField() + site = serializers.SerializerMethodField() + poi = serializers.SerializerMethodField() + license = serializers.SlugRelatedField( + read_only=True, + slug_field='label' + ) + + def get_picture_tiles_url(self, obj): + return build_url(self, obj.get_generic_picture_tile_url()) + + def get_thumbnail_url(self, obj): + return build_url(self, obj.thumbnail_url) + + def get_trek(self, obj): + related_obj = obj.content_object + if isinstance(related_obj, trekking_models.Trek): + return {'uuid': related_obj.uuid, 'id': related_obj.id} + return None + + def get_site(self, obj): + related_obj = obj.content_object + if isinstance(related_obj, outdoor_models.Site): + return {'uuid': related_obj.uuid, 'id': related_obj.id} + return None + + def get_poi(self, obj): + related_obj = obj.content_object + if isinstance(related_obj, trekking_models.POI): + return {'uuid': related_obj.uuid, 'id': related_obj.id} + return None + + class Meta(TimeStampedSerializer.Meta): + model = common_models.HDViewPoint + fields = TimeStampedSerializer.Meta.fields + ( + 'id', 'annotations', 'author', 'geometry', 'legend', 'license', 'picture_tiles_url', + 'poi', 'title', 'site', 'trek', 'thumbnail_url', 'uuid' + ) + + if 'geotrek.tourism' in settings.INSTALLED_APPS: class LabelAccessibilitySerializer(DynamicFieldsMixin, serializers.ModelSerializer): label = serializers.SerializerMethodField() @@ -382,11 +434,9 @@ class Meta: model = tourism_models.TouristicEventType fields = ('id', 'pictogram', 'type') - class TouristicModelSerializer(PDFSerializerMixin, DynamicFieldsMixin, serializers.ModelSerializer): + class TouristicModelSerializer(PDFSerializerMixin, DynamicFieldsMixin, TimeStampedSerializer): geometry = geo_serializers.GeometryField(read_only=True, source="geom_transformed", precision=7) accessibility = serializers.SerializerMethodField() - create_datetime = serializers.DateTimeField(source='date_insert') - update_datetime = serializers.DateTimeField(source='date_update') external_id = serializers.CharField(source='eid') cities = serializers.SerializerMethodField() name = serializers.SerializerMethodField() @@ -419,15 +469,15 @@ class TouristicContentSerializer(TouristicModelSerializer): types = serializers.SerializerMethodField() url = HyperlinkedIdentityField(view_name='apiv2:touristiccontent-detail') - class Meta: + class Meta(TimeStampedSerializer.Meta): model = tourism_models.TouristicContent - fields = ( + fields = TimeStampedSerializer.Meta.fields + ( 'id', 'accessibility', 'attachments', 'approved', 'category', 'description', 'description_teaser', 'departure_city', 'geometry', 'label_accessibility', - 'practical_info', 'url', 'cities', 'create_datetime', + 'practical_info', 'url', 'cities', 'external_id', 'name', 'pdf', 'portal', 'provider', 'published', 'source', 'structure', 'themes', - 'update_datetime', 'types', 'contact', 'email', + 'types', 'contact', 'email', 'website', 'reservation_system', 'reservation_id', 'uuid' ) @@ -478,17 +528,17 @@ def get_participant_number(self, obj): def get_end_date(self, obj): return obj.end_date or obj.begin_date - class Meta: + class Meta(TimeStampedSerializer.Meta): model = tourism_models.TouristicEvent - fields = ( + fields = TimeStampedSerializer.Meta.fields + ( 'id', 'accessibility', 'approved', 'attachments', 'begin_date', 'bookable', 'booking', 'cancellation_reason', 'cancelled', 'capacity', 'cities', - 'contact', 'create_datetime', 'description', 'description_teaser', 'duration', + 'contact', 'description', 'description_teaser', 'duration', 'email', 'end_date', 'end_time', 'external_id', 'geometry', 'meeting_point', 'meeting_time', 'name', 'organizer', 'participant_number', 'pdf', 'place', 'portal', 'practical_info', 'provider', 'published', 'source', 'speaker', 'start_time', 'structure', 'target_audience', 'themes', 'type', - 'update_datetime', 'url', 'uuid', 'website' + 'url', 'uuid', 'website' ) class TouristicEventPlaceSerializer(serializers.ModelSerializer): @@ -579,8 +629,8 @@ class TrekSerializer(PDFSerializerMixin, DynamicFieldsMixin, serializers.ModelSe arrival = serializers.SerializerMethodField() external_id = serializers.CharField(source='eid') second_external_id = serializers.CharField(source='eid2') - create_datetime = serializers.SerializerMethodField() - update_datetime = serializers.SerializerMethodField() + create_datetime = serializers.DateTimeField(source='topo_object.date_insert') + update_datetime = serializers.DateTimeField(source='topo_object.date_update') attachments = AttachmentSerializer(many=True, source='sorted_attachments') attachments_accessibility = AttachmentAccessibilitySerializer(many=True) gear = serializers.SerializerMethodField() @@ -603,16 +653,11 @@ class TrekSerializer(PDFSerializerMixin, DynamicFieldsMixin, serializers.ModelSe cities = serializers.SerializerMethodField() departure_city = serializers.SerializerMethodField() web_links = WebLinkSerializer(many=True) + view_points = HDViewPointSerializer(many=True) def get_gear(self, obj): return get_translation_or_dict('gear', self, obj) - def get_update_datetime(self, obj): - return obj.topo_object.date_update - - def get_create_datetime(self, obj): - return obj.topo_object.date_insert - def get_published(self, obj): return get_translation_or_dict('published', self, obj) @@ -755,7 +800,7 @@ class Meta: 'parents', 'parking_location', 'pdf', 'points_reference', 'portal', 'practice', 'provider', 'ratings', 'ratings_description', 'previous', 'public_transport', 'published', 'reservation_system', 'reservation_id', 'route', 'second_external_id', - 'source', 'structure', 'themes', 'update_datetime', 'url', 'uuid', 'web_links' + 'source', 'structure', 'themes', 'update_datetime', 'url', 'uuid', 'view_points', 'web_links' ) class TourSerializer(TrekSerializer): @@ -801,6 +846,7 @@ class POISerializer(DynamicFieldsMixin, serializers.ModelSerializer): update_datetime = serializers.DateTimeField(source='topo_object.date_update') geometry = geo_serializers.GeometryField(read_only=True, source="geom3d_transformed", precision=7) attachments = AttachmentSerializer(many=True, source='sorted_attachments') + view_points = HDViewPointSerializer(many=True) def get_type_label(self, obj): return get_translation_or_dict('label', self, obj.type) @@ -820,7 +866,7 @@ class Meta: 'id', 'description', 'external_id', 'geometry', 'name', 'attachments', 'provider', 'published', 'type', 'type_label', 'type_pictogram', 'url', 'uuid', - 'create_datetime', 'update_datetime' + 'create_datetime', 'update_datetime', 'view_points' ) class ThemeSerializer(DynamicFieldsMixin, serializers.ModelSerializer): @@ -854,7 +900,7 @@ class Meta: fields = ('id', 'name') if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - class SensitiveAreaSerializer(DynamicFieldsMixin, serializers.ModelSerializer): + class SensitiveAreaSerializer(DynamicFieldsMixin, TimeStampedSerializer): url = HyperlinkedIdentityField(view_name='apiv2:sensitivearea-detail') name = serializers.SerializerMethodField() elevation = serializers.SerializerMethodField() @@ -863,8 +909,6 @@ class SensitiveAreaSerializer(DynamicFieldsMixin, serializers.ModelSerializer): practices = serializers.PrimaryKeyRelatedField(many=True, source='species.practices', read_only=True) info_url = serializers.URLField(source='species.url') structure = serializers.CharField(source='structure.name') - create_datetime = serializers.DateTimeField(source='date_insert') - update_datetime = serializers.DateTimeField(source='date_update') published = serializers.BooleanField() geometry = geo_serializers.GeometryField(read_only=True, source="geom_transformed", precision=7) species_id = serializers.SerializerMethodField() @@ -891,13 +935,13 @@ def get_kml_url(self, obj): url = reverse('sensitivity:sensitivearea_kml_detail', kwargs={'lang': get_language(), 'pk': obj.pk}) return build_url(self, url) - class Meta: + class Meta(TimeStampedSerializer.Meta): model = sensitivity_models.SensitiveArea - fields = ( - 'id', 'contact', 'create_datetime', 'description', 'elevation', + fields = TimeStampedSerializer.Meta.fields + ( + 'id', 'contact', 'description', 'elevation', 'geometry', 'info_url', 'kml_url', 'name', 'period', 'practices', 'published', 'species_id', 'provider', 'structure', - 'update_datetime', 'url' + 'url' ) class BubbleSensitiveAreaSerializer(SensitiveAreaSerializer): @@ -1052,6 +1096,7 @@ class SiteSerializer(PDFSerializerMixin, DynamicFieldsMixin, serializers.ModelSe pdf = serializers.SerializerMethodField('get_pdf_url') cities = serializers.SerializerMethodField() web_links = WebLinkSerializer(many=True) + view_points = HDViewPointSerializer(many=True) def get_cities(self, obj): return [city.code for city in obj.published_cities] @@ -1107,7 +1152,7 @@ class Meta: 'id', 'accessibility', 'advice', 'ambiance', 'attachments', 'cities', 'children', 'description', 'description_teaser', 'eid', 'geometry', 'information_desks', 'labels', 'managers', 'name', 'orientation', 'pdf', 'period', 'parent', 'portal', 'practice', 'provider', - 'ratings', 'sector', 'source', 'structure', 'themes', + 'ratings', 'sector', 'source', 'structure', 'themes', 'view_points', 'type', 'url', 'uuid', 'courses', 'web_links', 'wind', ) diff --git a/geotrek/api/v2/urls.py b/geotrek/api/v2/urls.py index 8cdc6617c9..4a4cfa00a2 100644 --- a/geotrek/api/v2/urls.py +++ b/geotrek/api/v2/urls.py @@ -79,6 +79,7 @@ router.register('signage_sealing', api_views.SealingViewSet, basename='signage-sealing') router.register('signage_color', api_views.ColorViewSet, basename='signage-color') router.register('signage_direction', api_views.DirectionViewSet, basename='signage-direction') + router.register('hdviewpoint', api_views.HDViewPointViewSet, basename='hdviewpoint') app_name = 'apiv2' diff --git a/geotrek/api/v2/views/__init__.py b/geotrek/api/v2/views/__init__.py index 6fb75f3212..3a5777a29d 100644 --- a/geotrek/api/v2/views/__init__.py +++ b/geotrek/api/v2/views/__init__.py @@ -6,7 +6,7 @@ from geotrek import __version__ from .authent import StructureViewSet # noqa -from .common import TargetPortalViewSet, ThemeViewSet, SourceViewSet, ReservationSystemViewSet, LabelViewSet, OrganismViewSet, FileTypeViewSet # noqa +from .common import TargetPortalViewSet, ThemeViewSet, SourceViewSet, ReservationSystemViewSet, LabelViewSet, OrganismViewSet, FileTypeViewSet, HDViewPointViewSet # noqa if 'geotrek.core' in settings.INSTALLED_APPS: from .core import PathViewSet # noqa if 'geotrek.feedback' in settings.INSTALLED_APPS: diff --git a/geotrek/api/v2/views/common.py b/geotrek/api/v2/views/common.py index b202f11511..55872d4330 100644 --- a/geotrek/api/v2/views/common.py +++ b/geotrek/api/v2/views/common.py @@ -1,7 +1,10 @@ from hashlib import md5 from django.conf import settings +from django.contrib.gis.db.models.functions import Transform +from django.db.models import F from django.shortcuts import get_object_or_404 +from django.utils.translation import activate from rest_framework.response import Response @@ -107,3 +110,15 @@ class OrganismViewSet(api_viewsets.GeotrekViewSet): class FileTypeViewSet(api_viewsets.GeotrekViewSet): serializer_class = api_serializers.FileTypeSerializer queryset = common_models.FileType.objects.all() + + +class HDViewPointViewSet(api_viewsets.GeotrekGeometricViewset): + serializer_class = api_serializers.HDViewPointSerializer + filter_backends = api_viewsets.GeotrekGeometricViewset.filter_backends + (api_filters.TrekAndSiteAndPOIRelatedPublishedNotDeletedByPortalFilter,) + + def get_queryset(self): + activate(self.request.GET.get('language')) + return common_models.HDViewPoint.objects \ + .prefetch_related('content_object') \ + .annotate(geom_transformed=Transform(F('geom'), settings.API_SRID)) \ + .order_by('title') # Required for reliable pagination diff --git a/geotrek/api/v2/views/outdoor.py b/geotrek/api/v2/views/outdoor.py index a67a6fd119..3b6d5fa066 100644 --- a/geotrek/api/v2/views/outdoor.py +++ b/geotrek/api/v2/views/outdoor.py @@ -6,7 +6,7 @@ from geotrek.api.v2 import serializers as api_serializers, \ filters as api_filters, viewsets as api_viewsets -from geotrek.common.models import Attachment +from geotrek.common.models import Attachment, HDViewPoint from geotrek.outdoor import models as outdoor_models @@ -24,7 +24,9 @@ def get_queryset(self): return outdoor_models.Site.objects \ .annotate(geom_transformed=Transform(F('geom'), settings.API_SRID)) \ .prefetch_related(Prefetch('attachments', - queryset=Attachment.objects.select_related('license', 'filetype', 'filetype__structure'))) \ + queryset=Attachment.objects.select_related('license', 'filetype', 'filetype__structure')), + Prefetch('view_points', + queryset=HDViewPoint.objects.select_related('content_type', 'license'))) \ .order_by('name') # Required for reliable pagination diff --git a/geotrek/api/v2/views/trekking.py b/geotrek/api/v2/views/trekking.py index 1b112c0755..a5c58e8693 100644 --- a/geotrek/api/v2/views/trekking.py +++ b/geotrek/api/v2/views/trekking.py @@ -11,7 +11,7 @@ from geotrek.api.v2.decorators import cache_response_detail from geotrek.api.v2.functions import Length3D from geotrek.api.v2.renderers import SVGProfileRenderer -from geotrek.common.models import Attachment, AccessibilityAttachment +from geotrek.common.models import Attachment, AccessibilityAttachment, HDViewPoint from geotrek.trekking import models as trekking_models @@ -39,7 +39,9 @@ def get_queryset(self): Prefetch('attachments_accessibility', queryset=AccessibilityAttachment.objects.select_related('license')), Prefetch('web_links', - queryset=trekking_models.WebLink.objects.select_related('category'))) \ + queryset=trekking_models.WebLink.objects.select_related('category')), + Prefetch('view_points', + queryset=HDViewPoint.objects.select_related('content_type', 'license'))) \ .annotate(geom3d_transformed=Transform(F('geom_3d'), settings.API_SRID), length_3d_m=Length3D('geom_3d')) \ .order_by("name") # Required for reliable pagination @@ -168,7 +170,9 @@ class POIViewSet(api_viewsets.GeotrekGeometricViewset): .select_related('topo_object', 'type', ) \ .prefetch_related('topo_object__aggregations', Prefetch('attachments', - queryset=Attachment.objects.select_related('license', 'filetype', 'filetype__structure'))) \ + queryset=Attachment.objects.select_related('license', 'filetype', 'filetype__structure')), + Prefetch('view_points', + queryset=HDViewPoint.objects.select_related('content_type', 'license'))) \ .annotate(geom3d_transformed=Transform(F('geom_3d'), settings.API_SRID)) \ .order_by('pk') # Required for reliable pagination diff --git a/geotrek/authent/locale/de/LC_MESSAGES/django.po b/geotrek/authent/locale/de/LC_MESSAGES/django.po index f0f15ab199..ef81ec09f5 100644 --- a/geotrek/authent/locale/de/LC_MESSAGES/django.po +++ b/geotrek/authent/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/authent/locale/en/LC_MESSAGES/django.po b/geotrek/authent/locale/en/LC_MESSAGES/django.po index f0f15ab199..ef81ec09f5 100644 --- a/geotrek/authent/locale/en/LC_MESSAGES/django.po +++ b/geotrek/authent/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/authent/locale/es/LC_MESSAGES/django.po b/geotrek/authent/locale/es/LC_MESSAGES/django.po index f0f15ab199..ef81ec09f5 100644 --- a/geotrek/authent/locale/es/LC_MESSAGES/django.po +++ b/geotrek/authent/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/authent/locale/fr/LC_MESSAGES/django.po b/geotrek/authent/locale/fr/LC_MESSAGES/django.po index 141293471c..cfdf2a60e3 100644 --- a/geotrek/authent/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/authent/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/authent/locale/it/LC_MESSAGES/django.po b/geotrek/authent/locale/it/LC_MESSAGES/django.po index f0f15ab199..ef81ec09f5 100644 --- a/geotrek/authent/locale/it/LC_MESSAGES/django.po +++ b/geotrek/authent/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/authent/locale/nl/LC_MESSAGES/django.po b/geotrek/authent/locale/nl/LC_MESSAGES/django.po index f0f15ab199..ef81ec09f5 100644 --- a/geotrek/authent/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/authent/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/cirkwi/locale/de/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/de/LC_MESSAGES/django.po index 98545a50b5..c355d7621d 100644 --- a/geotrek/cirkwi/locale/de/LC_MESSAGES/django.po +++ b/geotrek/cirkwi/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/cirkwi/locale/en/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/en/LC_MESSAGES/django.po index 98545a50b5..c355d7621d 100644 --- a/geotrek/cirkwi/locale/en/LC_MESSAGES/django.po +++ b/geotrek/cirkwi/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/cirkwi/locale/es/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/es/LC_MESSAGES/django.po index 98545a50b5..c355d7621d 100644 --- a/geotrek/cirkwi/locale/es/LC_MESSAGES/django.po +++ b/geotrek/cirkwi/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/cirkwi/locale/fr/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/fr/LC_MESSAGES/django.po index 240fb65b86..a72ee443bf 100644 --- a/geotrek/cirkwi/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/cirkwi/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/cirkwi/locale/it/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/it/LC_MESSAGES/django.po index 98545a50b5..c355d7621d 100644 --- a/geotrek/cirkwi/locale/it/LC_MESSAGES/django.po +++ b/geotrek/cirkwi/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/cirkwi/locale/nl/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/nl/LC_MESSAGES/django.po index 98545a50b5..c355d7621d 100644 --- a/geotrek/cirkwi/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/cirkwi/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/common/admin.py b/geotrek/common/admin.py index c74dae41ce..4c7a3749b5 100644 --- a/geotrek/common/admin.py +++ b/geotrek/common/admin.py @@ -4,7 +4,9 @@ from django.urls import NoReverseMatch from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ + from geotrek.common.mixins.actions import MergeActionMixin + from . import models as common_models if 'modeltranslation' in settings.INSTALLED_APPS: @@ -58,8 +60,7 @@ class AttachmentAdmin(admin.ModelAdmin): readonly_fields = ('content_type', 'content_link', 'creator', 'title') def has_add_permission(self, request): - """ Do not add from Adminsite. - """ + """ Do not add from Adminsite. """ return False def content_link(self, obj): @@ -104,6 +105,38 @@ class LabelAdmin(TabbedTranslationAdmin): search_fields = ('name', ) +class HDViewPointAdmin(admin.ModelAdmin): + date_hierarchy = 'date_update' + search_fields = ('title', 'legend', 'author', 'object_id') + list_display = ('update_link', 'legend', 'author', 'related_object_link', 'content_type', 'license') + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.list_display_links = None + + def has_add_permission(self, request): + """ Do not add from Adminsite. """ + return False + + def update_link(self, obj): + """Returns link to HD View""" + return format_html( + '{}', + obj.pk, obj.full_url, obj.title + ) + + def related_object_link(self, obj): + """Returns content object link""" + content_url = obj.content_object.get_detail_url() + return format_html( + '{}', + obj.object_id, content_url, str(obj.content_object) + ) + + related_object_link.short_description = _('Related to') + update_link.short_description = _('Title') + + admin.site.register(common_models.Organism, OrganismAdmin) admin.site.register(common_models.Attachment, AttachmentAdmin) admin.site.register(common_models.FileType, FileTypeAdmin) @@ -113,3 +146,4 @@ class LabelAdmin(TabbedTranslationAdmin): admin.site.register(common_models.ReservationSystem, ReservationSystemAdmin) admin.site.register(common_models.Label, LabelAdmin) admin.site.register(common_models.License, LicenseAdmin) +admin.site.register(common_models.HDViewPoint, HDViewPointAdmin) diff --git a/geotrek/common/filters.py b/geotrek/common/filters.py index 5b62d48ce0..78c5a0bc8f 100644 --- a/geotrek/common/filters.py +++ b/geotrek/common/filters.py @@ -1,6 +1,9 @@ from django.utils.translation import gettext_lazy as _ +from django_filters import ModelMultipleChoiceFilter, RangeFilter +from mapentity.filters import MapEntityFilterSet + +from geotrek.common.models import HDViewPoint -from django_filters import RangeFilter, ModelMultipleChoiceFilter from .fields import OneLineRangeField @@ -27,3 +30,10 @@ def get_queryset(self, request=None): if self.queryset is not None: return self.queryset return self.model.objects.all() + + +class HDViewPointFilterSet(MapEntityFilterSet): + + class Meta(MapEntityFilterSet.Meta): + model = HDViewPoint + fields = ['title'] diff --git a/geotrek/common/forms.py b/geotrek/common/forms.py index 8721b8a1eb..b764ad722d 100644 --- a/geotrek/common/forms.py +++ b/geotrek/common/forms.py @@ -1,33 +1,32 @@ +import logging from copy import deepcopy +from crispy_forms.bootstrap import FormActions +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Button, Div, Layout, Submit from django import forms from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core.checks.messages import Error +from django.core.exceptions import FieldDoesNotExist, ValidationError from django.core.files.images import get_image_dimensions from django.db.models import Q -from django.db.models.query import QuerySet from django.db.models.fields.related import ForeignKey, ManyToManyField -from django.core.exceptions import FieldDoesNotExist, ValidationError +from django.db.models.query import QuerySet from django.forms.widgets import HiddenInput from django.urls import reverse from django.utils.text import format_lazy from django.utils.translation import gettext_lazy as _ +from mapentity.forms import MapEntityForm, SubmitButton -from mapentity.forms import MapEntityForm - -from geotrek.authent.models import default_structure, StructureRelated, StructureOrNoneRelated -from geotrek.common.models import AccessibilityAttachment +from geotrek.authent.models import (StructureOrNoneRelated, StructureRelated, + default_structure) from geotrek.common.mixins.models import PublishableMixin +from geotrek.common.models import AccessibilityAttachment, HDViewPoint from geotrek.common.utils.translation import get_translated_fields from .mixins.models import NoDeleteMixin -from crispy_forms.helper import FormHelper -from crispy_forms.layout import Layout, Div, Submit, Button -from crispy_forms.bootstrap import FormActions - -import logging - logger = logging.getLogger(__name__) @@ -463,3 +462,83 @@ def save(self, request, *args, **kwargs): self.instance.creator = request.user self.instance.content_object = obj return super().save(*args, **kwargs) + + +class HDViewPointForm(MapEntityForm): + geomfields = ['geom'] + + def __init__(self, *args, content_type=None, object_id=None, **kwargs): + super().__init__(*args, **kwargs) + if content_type and object_id: + ct = ContentType.objects.get_for_id(content_type) + self.instance.content_type = ct + self.instance.content_object = ct.get_object_for_this_type(id=object_id) + self.instance.object_id = object_id + self.helper.form_action += f"?object_id={object_id}&content_type={content_type}" + + class Meta: + model = HDViewPoint + fields = ('picture', 'geom', 'author', 'title', 'license', 'legend') + + +class HDViewPointAnnotationForm(forms.ModelForm): + annotations = forms.JSONField(label=False) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.fields['annotations'].required = False + self.fields['annotations'].widget = forms.Textarea( + attrs={ + 'name': 'annotations', + 'rows': '15', + 'type': 'textarea', + 'autocomplete': 'off', + 'autocorrect': 'off', + 'autocapitalize': 'off', + 'spellcheck': 'false', + # Do not show GEOJson textarea to users + 'style': 'display: none;' + } + ) + self._init_layout() + + def _init_layout(self): + """ Setup form buttons, submit URL, layout """ + + actions = [ + Button('cancel', _('Cancel'), css_class="btn btn-light ml-auto mr-2"), + SubmitButton('save_changes', _('Save changes')), + ] + + leftpanel = Div( + 'annotations', + css_id="modelfields", + ) + formactions = FormActions( + *actions, + css_class="form-actions", + template='mapentity/crispy_forms/bootstrap4/layout/formactions.html' + ) + + # # Main form layout + self.helper.help_text_inline = True + self.helper.form_class = 'form-horizontal' + self.helper.form_style = "default" + self.helper.label_class = 'col-md-3' + self.helper.field_class = 'controls col-md-9' + self.helper.layout = Layout( + Div( + Div( + leftpanel, + # *rightpanel, + css_class="row" + ), + css_class="container-fluid" + ), + formactions, + ) + + class Meta: + model = HDViewPoint + fields = ('annotations', ) diff --git a/geotrek/common/locale/de/LC_MESSAGES/django.po b/geotrek/common/locale/de/LC_MESSAGES/django.po index 60a1244c61..a22c1c3211 100644 --- a/geotrek/common/locale/de/LC_MESSAGES/django.po +++ b/geotrek/common/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -24,6 +24,12 @@ msgstr "" msgid "Linked content" msgstr "" +msgid "Related to" +msgstr "" + +msgid "Title" +msgstr "" + msgid "Common" msgstr "" @@ -93,6 +99,9 @@ msgstr "" msgid "The uploaded file is not tall enough" msgstr "" +msgid "Save changes" +msgstr "" + msgid "Global tiles syncing ..." msgstr "" @@ -327,6 +336,30 @@ msgstr "" msgid "Color" msgstr "" +msgid "Picture" +msgstr "" + +msgid "Location" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Title for this view point" +msgstr "" + +msgid "Details about this view" +msgstr "" + +msgid "HD View" +msgstr "" + +msgid "HD Views" +msgstr "" + +msgid "Add a new HD view" +msgstr "" + #, python-brace-format msgid "Line {line}" msgstr "" @@ -500,6 +533,62 @@ msgstr "" msgid "Sync mobile" msgstr "" +msgid "Rectangle" +msgstr "" + +msgid "Square" +msgstr "" + +msgid "Ellipse" +msgstr "" + +msgid "Circle" +msgstr "" + +msgid "Polygon" +msgstr "" + +msgid "Point" +msgstr "" + +msgid "Line" +msgstr "" + +msgid "Created annotations" +msgstr "" + +msgid "Properties" +msgstr "" + +msgid "History" +msgstr "" + +msgid "Annotate" +msgstr "" + +msgid "Attributes" +msgstr "" + +msgid "Date" +msgstr "" + +msgid "User" +msgstr "" + +msgid "Action" +msgstr "" + +msgid "Full history" +msgstr "" + +msgid "" +"Add very high resolution images that will be automatically tiled and add " +"annotations on it (points, lines, polygons and text)" +msgstr "" + +msgid "You are not allowed to see HD views." +msgstr "" + msgid "Decoding error. Please check encoding and use only ASCII in file names." msgstr "" diff --git a/geotrek/common/locale/en/LC_MESSAGES/django.po b/geotrek/common/locale/en/LC_MESSAGES/django.po index 60a1244c61..a22c1c3211 100644 --- a/geotrek/common/locale/en/LC_MESSAGES/django.po +++ b/geotrek/common/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -24,6 +24,12 @@ msgstr "" msgid "Linked content" msgstr "" +msgid "Related to" +msgstr "" + +msgid "Title" +msgstr "" + msgid "Common" msgstr "" @@ -93,6 +99,9 @@ msgstr "" msgid "The uploaded file is not tall enough" msgstr "" +msgid "Save changes" +msgstr "" + msgid "Global tiles syncing ..." msgstr "" @@ -327,6 +336,30 @@ msgstr "" msgid "Color" msgstr "" +msgid "Picture" +msgstr "" + +msgid "Location" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Title for this view point" +msgstr "" + +msgid "Details about this view" +msgstr "" + +msgid "HD View" +msgstr "" + +msgid "HD Views" +msgstr "" + +msgid "Add a new HD view" +msgstr "" + #, python-brace-format msgid "Line {line}" msgstr "" @@ -500,6 +533,62 @@ msgstr "" msgid "Sync mobile" msgstr "" +msgid "Rectangle" +msgstr "" + +msgid "Square" +msgstr "" + +msgid "Ellipse" +msgstr "" + +msgid "Circle" +msgstr "" + +msgid "Polygon" +msgstr "" + +msgid "Point" +msgstr "" + +msgid "Line" +msgstr "" + +msgid "Created annotations" +msgstr "" + +msgid "Properties" +msgstr "" + +msgid "History" +msgstr "" + +msgid "Annotate" +msgstr "" + +msgid "Attributes" +msgstr "" + +msgid "Date" +msgstr "" + +msgid "User" +msgstr "" + +msgid "Action" +msgstr "" + +msgid "Full history" +msgstr "" + +msgid "" +"Add very high resolution images that will be automatically tiled and add " +"annotations on it (points, lines, polygons and text)" +msgstr "" + +msgid "You are not allowed to see HD views." +msgstr "" + msgid "Decoding error. Please check encoding and use only ASCII in file names." msgstr "" diff --git a/geotrek/common/locale/es/LC_MESSAGES/django.po b/geotrek/common/locale/es/LC_MESSAGES/django.po index 2f4dc319f7..ba1ae09a4e 100644 --- a/geotrek/common/locale/es/LC_MESSAGES/django.po +++ b/geotrek/common/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Olivia Duval \n" "Language-Team: LANGUAGE \n" @@ -24,6 +24,12 @@ msgstr "" msgid "Linked content" msgstr "" +msgid "Related to" +msgstr "" + +msgid "Title" +msgstr "" + msgid "Common" msgstr "" @@ -93,6 +99,9 @@ msgstr "" msgid "The uploaded file is not tall enough" msgstr "" +msgid "Save changes" +msgstr "" + msgid "Global tiles syncing ..." msgstr "" @@ -327,6 +336,32 @@ msgstr "" msgid "Color" msgstr "" +#, fuzzy +#| msgid "Pictogram" +msgid "Picture" +msgstr "Pictograma" + +msgid "Location" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Title for this view point" +msgstr "" + +msgid "Details about this view" +msgstr "" + +msgid "HD View" +msgstr "" + +msgid "HD Views" +msgstr "" + +msgid "Add a new HD view" +msgstr "" + #, python-brace-format msgid "Line {line}" msgstr "" @@ -500,6 +535,62 @@ msgstr "" msgid "Sync mobile" msgstr "" +msgid "Rectangle" +msgstr "" + +msgid "Square" +msgstr "" + +msgid "Ellipse" +msgstr "" + +msgid "Circle" +msgstr "" + +msgid "Polygon" +msgstr "" + +msgid "Point" +msgstr "" + +msgid "Line" +msgstr "" + +msgid "Created annotations" +msgstr "" + +msgid "Properties" +msgstr "" + +msgid "History" +msgstr "" + +msgid "Annotate" +msgstr "" + +msgid "Attributes" +msgstr "" + +msgid "Date" +msgstr "" + +msgid "User" +msgstr "" + +msgid "Action" +msgstr "" + +msgid "Full history" +msgstr "" + +msgid "" +"Add very high resolution images that will be automatically tiled and add " +"annotations on it (points, lines, polygons and text)" +msgstr "" + +msgid "You are not allowed to see HD views." +msgstr "" + msgid "Decoding error. Please check encoding and use only ASCII in file names." msgstr "" diff --git a/geotrek/common/locale/fr/LC_MESSAGES/django.po b/geotrek/common/locale/fr/LC_MESSAGES/django.po index 70ae429b1d..857b7d4d1b 100644 --- a/geotrek/common/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/common/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-09-23 07:10+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" @@ -24,6 +24,12 @@ msgstr "" msgid "Linked content" msgstr "" +msgid "Related to" +msgstr "" + +msgid "Title" +msgstr "" + msgid "Common" msgstr "" @@ -93,6 +99,9 @@ msgstr "" msgid "The uploaded file is not tall enough" msgstr "" +msgid "Save changes" +msgstr "" + msgid "Global tiles syncing ..." msgstr "" @@ -327,6 +336,30 @@ msgstr "" msgid "Color" msgstr "" +msgid "Picture" +msgstr "" + +msgid "Location" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Title for this view point" +msgstr "" + +msgid "Details about this view" +msgstr "" + +msgid "HD View" +msgstr "" + +msgid "HD Views" +msgstr "" + +msgid "Add a new HD view" +msgstr "" + #, python-brace-format msgid "Line {line}" msgstr "" @@ -500,6 +533,62 @@ msgstr "" msgid "Sync mobile" msgstr "" +msgid "Rectangle" +msgstr "" + +msgid "Square" +msgstr "" + +msgid "Ellipse" +msgstr "" + +msgid "Circle" +msgstr "" + +msgid "Polygon" +msgstr "" + +msgid "Point" +msgstr "" + +msgid "Line" +msgstr "" + +msgid "Created annotations" +msgstr "" + +msgid "Properties" +msgstr "" + +msgid "History" +msgstr "" + +msgid "Annotate" +msgstr "" + +msgid "Attributes" +msgstr "" + +msgid "Date" +msgstr "" + +msgid "User" +msgstr "" + +msgid "Action" +msgstr "" + +msgid "Full history" +msgstr "" + +msgid "" +"Add very high resolution images that will be automatically tiled and add " +"annotations on it (points, lines, polygons and text)" +msgstr "" + +msgid "You are not allowed to see HD views." +msgstr "" + msgid "Decoding error. Please check encoding and use only ASCII in file names." msgstr "" diff --git a/geotrek/common/locale/nl/LC_MESSAGES/django.po b/geotrek/common/locale/nl/LC_MESSAGES/django.po index 60a1244c61..a22c1c3211 100644 --- a/geotrek/common/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/common/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -24,6 +24,12 @@ msgstr "" msgid "Linked content" msgstr "" +msgid "Related to" +msgstr "" + +msgid "Title" +msgstr "" + msgid "Common" msgstr "" @@ -93,6 +99,9 @@ msgstr "" msgid "The uploaded file is not tall enough" msgstr "" +msgid "Save changes" +msgstr "" + msgid "Global tiles syncing ..." msgstr "" @@ -327,6 +336,30 @@ msgstr "" msgid "Color" msgstr "" +msgid "Picture" +msgstr "" + +msgid "Location" +msgstr "" + +msgid "Annotations" +msgstr "" + +msgid "Title for this view point" +msgstr "" + +msgid "Details about this view" +msgstr "" + +msgid "HD View" +msgstr "" + +msgid "HD Views" +msgstr "" + +msgid "Add a new HD view" +msgstr "" + #, python-brace-format msgid "Line {line}" msgstr "" @@ -500,6 +533,62 @@ msgstr "" msgid "Sync mobile" msgstr "" +msgid "Rectangle" +msgstr "" + +msgid "Square" +msgstr "" + +msgid "Ellipse" +msgstr "" + +msgid "Circle" +msgstr "" + +msgid "Polygon" +msgstr "" + +msgid "Point" +msgstr "" + +msgid "Line" +msgstr "" + +msgid "Created annotations" +msgstr "" + +msgid "Properties" +msgstr "" + +msgid "History" +msgstr "" + +msgid "Annotate" +msgstr "" + +msgid "Attributes" +msgstr "" + +msgid "Date" +msgstr "" + +msgid "User" +msgstr "" + +msgid "Action" +msgstr "" + +msgid "Full history" +msgstr "" + +msgid "" +"Add very high resolution images that will be automatically tiled and add " +"annotations on it (points, lines, polygons and text)" +msgstr "" + +msgid "You are not allowed to see HD views." +msgstr "" + msgid "Decoding error. Please check encoding and use only ASCII in file names." msgstr "" diff --git a/geotrek/common/migrations/0029_hdviewpoint.py b/geotrek/common/migrations/0029_hdviewpoint.py new file mode 100644 index 0000000000..1a68f39539 --- /dev/null +++ b/geotrek/common/migrations/0029_hdviewpoint.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.16 on 2022-12-20 16:02 + +from django.conf import settings +import django.contrib.gis.db.models.fields +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('common', '0028_alter_recordsource_name'), + ] + + operations = [ + migrations.CreateModel( + name='HDViewPoint', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_insert', models.DateTimeField(auto_now_add=True, verbose_name='Insertion date')), + ('date_update', models.DateTimeField(auto_now=True, db_index=True, verbose_name='Update date')), + ('picture', models.FileField(upload_to='hdviewpoints/', verbose_name='Picture')), + ('geom', django.contrib.gis.db.models.fields.PointField(srid=settings.SRID, verbose_name='Location')), + ('object_id', models.PositiveIntegerField()), + ('annotations', models.JSONField(blank=True, default=dict, verbose_name='Annotations')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), + ('author', models.CharField(blank=True, default='', help_text='Original creator', max_length=128, verbose_name='Author')), + ('title', models.CharField(help_text='Title for this view point', max_length=1024, verbose_name='Title')), + ('legend', models.CharField(blank=True, default='', help_text='Details about this view', max_length=1024, verbose_name='Legend')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.license', verbose_name='License')), + ], + options={ + 'verbose_name': 'HD View', + 'verbose_name_plural': 'HD Views', + 'permissions': (('read_hdviewpoint', 'Can read hd view point'),), + }, + ), + ] diff --git a/geotrek/common/models.py b/geotrek/common/models.py index 02a4957656..7c00d633d5 100755 --- a/geotrek/common/models.py +++ b/geotrek/common/models.py @@ -1,20 +1,28 @@ import os import uuid -from PIL import Image from colorfield.fields import ColorField from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType +from django.contrib.gis.db import models as gis_models from django.db import models from django.db.models import Q from django.template.defaultfilters import slugify +from django.urls import reverse +from django.utils.http import urlencode from django.utils.translation import gettext_lazy as _ -from paperclip.models import Attachment as BaseAttachment, FileType as BaseFileType, License as BaseLicense +from mapentity.models import MapEntityMixin +from paperclip.models import Attachment as BaseAttachment +from paperclip.models import FileType as BaseFileType +from paperclip.models import License as BaseLicense +from PIL import Image from geotrek.authent.models import StructureOrNoneRelated + from .managers import AccessibilityAttachmentManager -from .mixins.models import OptionalPictogramMixin, PictogramMixin, TimeStampedModelMixin +from .mixins.models import (OptionalPictogramMixin, PictogramMixin, + TimeStampedModelMixin) def attachment_accessibility_upload(instance, filename): @@ -264,3 +272,60 @@ def __str__(self): class Meta: abstract = True + + +class HDViewPoint(TimeStampedModelMixin, MapEntityMixin): + picture = models.FileField(verbose_name=_("Picture"), upload_to="hdviewpoints/") + geom = gis_models.PointField(verbose_name=_("Location"), + srid=settings.SRID) + object_id = models.PositiveIntegerField() + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + content_object = GenericForeignKey('content_type', 'object_id') + annotations = models.JSONField(verbose_name=_("Annotations"), blank=True, default=dict) + uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) + author = models.CharField(blank=True, default='', max_length=128, + verbose_name=_('Author'), + help_text=_("Original creator")) + title = models.CharField(max_length=1024, + verbose_name=_("Title"), + help_text=_("Title for this view point")) + legend = models.CharField(blank=True, default='', max_length=1024, + verbose_name=_("Legend"), + help_text=_("Details about this view")) + license = models.ForeignKey(settings.PAPERCLIP_LICENSE_MODEL, + verbose_name=_("License"), + null=True, blank=True, + on_delete=models.SET_NULL) + + class Meta: + verbose_name = _("HD View") + verbose_name_plural = _("HD Views") + permissions = ( + ("read_hdviewpoint", "Can read hd view point"), + ) + + def __str__(self): + return self.title + + @property + def full_url(self): + return reverse('common:hdviewpoint_detail', kwargs={'pk': self.pk}) + + @classmethod + def get_list_url(cls): + return reverse('admin:common_hdviewpoint_changelist') + + def get_picture_tile_url(self, x, y, z): + url = reverse("common:hdviewpoint-tile", kwargs={'pk': self.pk, 'x': x, 'y': y, 'z': z, 'fmt': 'png'}) + return f"{url}?{urlencode({'source': 'vips'})}" + + def get_generic_picture_tile_url(self): + url = self.get_picture_tile_url(0, 0, 0).replace("/0/0/0.png", "/{z}/{x}/{y}.png") + return url + + @property + def thumbnail_url(self): + return reverse('common:hdviewpoint-thumbnail', kwargs={'pk': self.pk, 'fmt': 'png'}) + + def get_annotate_url(self): + return reverse('common:hdviewpoint_annotate', args=[self.pk]) diff --git a/geotrek/common/permissions.py b/geotrek/common/permissions.py index 5fb80034fd..9af937be02 100644 --- a/geotrek/common/permissions.py +++ b/geotrek/common/permissions.py @@ -1,4 +1,5 @@ from django.core.exceptions import PermissionDenied +from mapentity.models import MapEntityRestPermissions class PublicOrReadPermMixin: @@ -11,3 +12,15 @@ def get_object(self, queryset=None): if not self.request.user.has_perm('%s.read_%s' % (obj._meta.app_label, obj._meta.model_name)): raise PermissionDenied return obj + + +class RelatedPublishedPermission(MapEntityRestPermissions): + + def has_object_permission(self, request, view, obj): + # This object is public if related object is published + # If unpublished, only logged in users can access it, if they have view rights + return super().has_permission(request, view) or obj.content_object.any_published + + def has_permission(self, request, view): + # Permissions are defined at object level here + return True diff --git a/geotrek/common/serializers.py b/geotrek/common/serializers.py index dd76f5f963..342f9a6585 100644 --- a/geotrek/common/serializers.py +++ b/geotrek/common/serializers.py @@ -1,13 +1,16 @@ from django.conf import settings -from django.urls import reverse from django.db import models as django_db_models from django.shortcuts import get_object_or_404 +from django.urls import reverse from django.utils.translation import get_language - +from mapentity.serializers import MapentityGeojsonModelSerializer from rest_framework import serializers as rest_serializers -from rest_framework import serializers as rest_fields +from rest_framework_gis.fields import (GeometryField, + GeometrySerializerMethodField) +from rest_framework_gis.serializers import GeoFeatureModelSerializer -from .models import Theme, RecordSource, TargetPortal, FileType, Attachment, Label +from .models import (Attachment, FileType, HDViewPoint, Label, RecordSource, + TargetPortal, Theme) class TranslatedModelSerializer(rest_serializers.ModelSerializer): @@ -19,7 +22,7 @@ def get_field(self, model_field): if model_field.null: kwargs['allow_none'] = True kwargs['max_length'] = getattr(model_field, 'max_length') - return rest_fields.CharField(**kwargs) + return rest_serializers.CharField(**kwargs) return super().get_field(model_field) @@ -92,3 +95,37 @@ class LabelSerializer(PictogramSerializerMixin, TranslatedModelSerializer): class Meta: model = Label fields = ('id', 'pictogram', 'name', 'advice', 'filter_rando') + + +class HDViewPointSerializer(TranslatedModelSerializer): + class Meta: + model = HDViewPoint + fields = ( + 'id', 'uuid', 'author', 'title', 'legend', 'license' + ) + + +class HDViewPointGeoJSONSerializer(MapentityGeojsonModelSerializer): + api_geom = GeometrySerializerMethodField() + + def get_api_geom(self, obj): + return obj.geom.transform(4326, clone=True) + + class Meta(MapentityGeojsonModelSerializer.Meta): + model = HDViewPoint + fields = ('id', 'title') + + +class HDViewPointAPISerializer(HDViewPointSerializer): + class Meta(HDViewPointSerializer.Meta): + id_field = 'id' + fields = HDViewPointSerializer.Meta.fields + + +class HDViewPointAPIGeoJSONSerializer(GeoFeatureModelSerializer, HDViewPointAPISerializer): + # Annotated geom field with API_SRID + api_geom = GeometryField(read_only=True, precision=7) + + class Meta(HDViewPointAPISerializer.Meta): + geo_field = 'api_geom' + fields = HDViewPointAPISerializer.Meta.fields + ('api_geom', ) diff --git a/geotrek/common/signals.py b/geotrek/common/signals.py index 8f1cf9bf37..6370a46bb7 100644 --- a/geotrek/common/signals.py +++ b/geotrek/common/signals.py @@ -2,13 +2,15 @@ from django.dispatch import receiver from django.utils.timezone import now -from geotrek.common.models import Attachment, AccessibilityAttachment +from geotrek.common.models import Attachment, AccessibilityAttachment, HDViewPoint @receiver(post_save, sender=Attachment) @receiver(post_save, sender=AccessibilityAttachment) +@receiver(post_save, sender=HDViewPoint) @receiver(post_delete, sender=Attachment) @receiver(post_delete, sender=AccessibilityAttachment) +@receiver(post_delete, sender=HDViewPoint) def update_content_object_date_update(sender, instance, *args, **kwargs): """ after each creation / edition / deletion, increment date_updated to avoid object cache """ content_object = instance.content_object diff --git a/geotrek/common/static/common/js/annotations.js b/geotrek/common/static/common/js/annotations.js new file mode 100644 index 0000000000..05108ec381 --- /dev/null +++ b/geotrek/common/static/common/js/annotations.js @@ -0,0 +1,338 @@ +/* Decode query components into a dictionary of values. + * + * @returns {object}: the query parameters as a dictionary. + */ +function getQuery() { + var query = document.location.search.replace(/(^\?)/, '').split( + '&').map(function (n) { + n = n.split('='); + if (n[0]) { + this[decodeURIComponent(n[0].replace(/\+/g, '%20'))] = decodeURIComponent(n[1].replace(/\+/g, '%20')); + } + return this; + }.bind({}))[0]; + return query; +} + +/* Encode a dictionary of parameters to the query string, setting the window + * location and history. This will also remove undefined values from the + * set properites of params. + * + * @param {object} params: the query parameters as a dictionary. + */ +function setQuery(params) { + $.each(params, function (key, value) { + if (value === undefined) { + delete params[key]; + } + }); + var newurl = window.location.protocol + '//' + window.location.host + + window.location.pathname + '?' + $.param(params); + window.history.replaceState(params, '', newurl); +} + +var query = getQuery(); + +$('#showLabels').prop('checked', query.labels !== 'false'); +if (query.lastannotation) { + $('.annotationtype button').removeClass('lastused'); + $('.annotationtype button#' + query.lastannotation).addClass('lastused'); +} + +function initAnnotationsWidget(map) { + + var annotationDebug = {}; + + var layer, fromButtonSelect, fromGeojsonUpdate; + + var initialGeoJSON = $('#id_annotations').text(); + + $('#controls').on('change', change_controls); + $('#id_annotations[type=textarea]').on('input propertychange', change_geojson); + $('#controls').on('click', 'a', select_control); + $('.annotationtype button').on('click', select_annotation); + + $('#controls').toggleClass('no-controls', query.controls === 'false'); + + layer = map.createLayer('annotation', { + renderer: query.renderer ? (query.renderer === 'html' ? null : query.renderer) : undefined, + annotations: query.renderer ? undefined : geo.listAnnotations(), + showLabels: query.labels !== 'false', + clickToEdit: true + }); + + layer.geoOn(geo.event.mouseclick, mouseClickToStart); + layer.geoOn(geo.event.annotation.mode, handleModeChange); + layer.geoOn(geo.event.annotation.add, handleAnnotationChange); + layer.geoOn(geo.event.annotation.update, handleAnnotationChange); + layer.geoOn(geo.event.annotation.remove, handleAnnotationChange); + layer.geoOn(geo.event.annotation.state, handleAnnotationChange); + + map.draw(); + + if (query.lastused || query.active) { + if (query.active) { + layer.mode(query.active); + } else { + $('.annotationtype button').removeClass('lastused active'); + $('.annotationtype button#' + (query.lastused || query.active)).addClass('lastused'); + } + } + + if (initialGeoJSON) { + layer.geojson(initialGeoJSON, true); + } + + annotationDebug.map = map; + annotationDebug.layer = layer; + annotationDebug.query = query; + + /** + * When the mouse is clicked, switch to adding an annotation if appropriate. + * + * @param {geo.event} evt geojs event. + */ + function mouseClickToStart(evt) { + if (evt.handled) { + return; + } + if (evt.buttonsDown.left) { + if ($('.annotationtype button.lastused').hasClass('active')) { + return; + } + select_button('.annotationtype button.lastused'); + } else if (evt.buttonsDown.right) { + select_button('.annotationtype button#' + + $('.annotationtype button.lastused').attr('next')); + } + } + + /** + * Handle changes to our controls. + * + * @param evt jquery evt that triggered this call. + */ + function change_controls(evt) { + var ctl = $(evt.target), + param = ctl.attr('param-name'), + value = ctl.val(); + if (ctl.is('[type="checkbox"]')) { + value = ctl.is(':checked') ? 'true' : 'false'; + } + if (value === '' && ctl.attr('placeholder')) { + value = ctl.attr('placeholder'); + } + if (!param || value === query[param]) { + return; + } + if (param == 'labels') { + layer.options('showLabels', '' + value !== 'false'); + layer.draw(); + } + query[param] = value; + if (value === '' || (ctl.attr('placeholder') && + value === ctl.attr('placeholder'))) { + delete query[param]; + } + setQuery(query); + } + + /** + * Handle changes to the geojson. + * + * @param evt jquery evt that triggered this call. + */ + function change_geojson(evt) { + var ctl = $(evt.target), + value = ctl.val(); + + + fromGeojsonUpdate = true; + var result = layer.geojson(value, 'update'); + if (query.save && result !== undefined) { + var geojson = layer.geojson(); + query.geojson = geojson ? JSON.stringify(geojson) : undefined; + setQuery(query); + } + fromGeojsonUpdate = false; + } + + /** + * Handle selecting an annotation button. + * + * @param evt jquery evt that triggered this call. + */ + function select_annotation(evt) { + select_button(evt.target); + } + + /** + * Select an annotation button by jquery selector. + * + * @param {object} ctl a jquery selector or element. + */ + function select_button(ctl) { + ctl = $(ctl); + var wasactive = ctl.hasClass('active'), + id = ctl.attr('id'); + fromButtonSelect = true; + layer.mode(wasactive ? null : id); + fromButtonSelect = false; + } + + /** + * When the annotation mode changes, update the controls to reflect it. + * + * @param {geo.event} evt a geojs mode change event. + */ + function handleModeChange(evt) { + var mode = layer.mode(); + $('.annotationtype button').removeClass('active'); + if (mode) { + $('.annotationtype button').removeClass('lastused active'); + $('.annotationtype button#' + mode).addClass('lastused active'); + } + $('#instructions').attr( + 'annotation', $('.annotationtype button.active').attr('id') || 'none'); + query.active = $('.annotationtype button.active').attr('id') || undefined; + query.lastused = query.active ? undefined : $('.annotationtype button.lastused').attr('id'); + setQuery(query); + + if (!mode && !fromButtonSelect) { + layer.mode($('.annotationtype button.lastused').attr('id')); + } + } + + /** + * When an annotation is created or removed, update our list of annotations. + * + * @param {geo.event} evt a geojs mode change event. + */ + function handleAnnotationChange(evt) { + var annotations = layer.annotations(); + var ids = annotations.map(function (annotation) { + return annotation.id(); + }); + var present = []; + $('#annotationlist .entry').each(function () { + var entry = $(this); + if (entry.attr('id') === 'sample') { + return; + } + var id = entry.attr('annotation-id'); + if ($.inArray(id, ids) < 0) { + entry.remove(); + return; + } + present.push(id); + entry.find('.entry-name').text(layer.annotationById(id).name()); + }); + + $.each(ids, function (idx, id) { + if ($.inArray(id, present) >= 0) { + return; + } + var annotation = layer.annotationById(id); + if (annotation.state() === geo.annotation.state.create) { + return; + } + var entry = $('#annotationlist .entry#sample').clone(); + entry.attr({ id: '', 'annotation-id': id }); + entry.on('click', () => edit_label(entry)); + entry.find('.entry-name').text(annotation.name()); + if (query.editing == id) { + entry.find('.entry-adjust').hide(); + entry.find('.entry-validate').show(); + } + $('#annotationlist').append(entry); + }); + $('#annotationheader').css( + 'display', $('#annotationlist .entry').length <= 1 ? 'none' : 'block'); + if (!fromGeojsonUpdate) { + var geojson = layer.geojson(); + $('#id_annotations').val(geojson ? JSON.stringify(geojson, undefined, 2) : ''); + if (query.save) { + query.geojson = geojson ? JSON.stringify(geojson) : undefined; + setQuery(query); + } + } + } + + function edit_label(entry) { + // When clicking annotation name, display text input allowing to change it + span = entry.find('.entry-name') + text_input = ``; + $(span).html(text_input); + $('#label_input').focus(); + $('#label_input').on('blur', () => update_edited_label(entry)); + $('#label_input').on('keydown', (key) => update_edited_label_on_enter(entry, key)); + } + + function update_edited_label_on_enter(entry, key) { + if (key.keyCode === 13) { + update_edited_label(entry) + } + } + + function update_edited_label(entry) { + // After changing annotation name in input, update it in geojson and layer + new_label = $('#label_input').val() + // Replace input with span again + span = entry.find('.entry-name') + span.text(new_label); + // Replace name in GEOJson form + annotation_id = entry[0].getAttribute('annotation-id'); + current_data = JSON.parse($('#id_annotations[type=textarea]').val()) + for (var i = 0; i < current_data['features'].length; ++i) { + if (current_data['features'][i]['properties']['annotationId'] == annotation_id) { + current_data['features'][i]['properties']['name'] = new_label; + } + } + $('#id_annotations[type=textarea]').val(JSON.stringify(current_data, null, 2)); + $('#id_annotations[type=textarea]').trigger('propertychange') + // Replace name in GEOJson layer + var annotation = layer.annotationById(annotation_id); + annotation.name(new_label); + } + + /** + * Handle selecting a control. + * + * @param evt jquery evt that triggered this call. + */ + function select_control(evt) { + var mode, + ctl = $(evt.target), + action = ctl.attr('action'), + entry = ctl.closest('.entry'), + id = entry.attr('annotation-id'), + annotation = layer.annotationById(id); + switch (action) { + case 'validate': + layer.mode(null); + query.editing = undefined; + setQuery(query) + layer.draw(); + handleAnnotationChange(evt); + break; + case 'adjust': + layer.mode(layer.modes.edit, annotation); + query.editing = id; + setQuery(query) + layer.draw(); + handleAnnotationChange(evt); + break; + case 'remove': + layer.removeAnnotation(annotation); + break; + case 'remove-all': + fromButtonSelect = true; + mode = layer.mode(); + layer.mode(null); + layer.removeAllAnnotations(); + layer.mode(mode); + fromButtonSelect = false; + break; + } + } +} diff --git a/geotrek/common/static/common/js/hdviewpoint_viewer.js b/geotrek/common/static/common/js/hdviewpoint_viewer.js new file mode 100644 index 0000000000..1a25a95edc --- /dev/null +++ b/geotrek/common/static/common/js/hdviewpoint_viewer.js @@ -0,0 +1,47 @@ +function initializeViewer(base_tile_url, edit_annotations = false) { + var tileUrl = base_tile_url + `/tiles/{z}/{x}/{y}.png?source=vips`; + var metadataUrl = base_tile_url + "/info/metadata?source=vips"; + var viewer; + + fetch(metadataUrl) + .then(response => response.json()) + .then(tileinfo => { + const params = geo.util.pixelCoordinateParams( + '#hdviewpoint-map', tileinfo.sizeX, tileinfo.sizeY, tileinfo.tileWidth, tileinfo.tileHeight); + params.layer.url = tileUrl; + viewer = geo.map(params.map); + viewer.zoomRange({ + // do not set a min limit so that bounds clamping determines min + min: -Infinity, + max: 12, + }); + viewer.createLayer('osm', params.layer); + + // Change default interactor options + const interactorOpts = viewer.interactor().options(); + interactorOpts.zoomAnimation = { + enabled: false, + }; + interactorOpts.momentum = { + enabled: true, + }; + viewer.interactor().options(interactorOpts); + + var ui = viewer.createLayer('ui'); + // Create a zoom slider widget + ui.createWidget('slider', { + position: { + left: 40, + top: 40 + } + }); + if (edit_annotations) { + initAnnotationsWidget(viewer); + } + else { + layer = viewer.createLayer('annotation'); + layer.geojson($('#geojson_annotations').text()); + } + $(".loader-wrapper").remove(); + }); +}; diff --git a/geotrek/common/static/common/style.css b/geotrek/common/static/common/style.css index b993942020..a17ca2b29b 100644 --- a/geotrek/common/static/common/style.css +++ b/geotrek/common/static/common/style.css @@ -17,4 +17,274 @@ fieldset { margin-left: 80px; +} + +#hdviewpoint-map { + width: 100%; + height: 550px; + padding: 0; + margin: 0; + overflow: hidden; + border: 1px solid lightgrey; + border-radius: 5px; +} + +#hdviewpoint-map.large { + height: 800px; + margin-right: 10px; +} + +.form-panel#annotations { + flex-basis: 70%; +} + +.loader-wrapper { + display: table; + margin: 0 auto; +} + +.loader { + width: 48px; + height: 48px; + border: 5px solid green; + border-bottom-color: transparent; + border-radius: 50%; + display: inline-block; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +#controls { + border-radius: 5px; + border: 1px solid lightgrey; + color: black; + padding: 10px; + max-height: calc(100% - 100px); + min-width: 310px; + margin-left: 10px; + margin-right: 10px; +} +#controls .form-group { + margin-bottom: 0; +} +#controls label { + min-width: 160px; +} +#controls.no-controls { + display: none; +} + +.annotationtype{ + justify-content: space-between; + display: flex; + margin-left: 10px; + margin-right: 10px; +} + +.annotationtype button { + display: inline-block; + outline: 0; + border: 0; + cursor: pointer; + background: #1e7e34; + color: #FFFFFF; + border-radius: 8px; + padding: 14px 24px 16px; + font-size: 14px; + font-weight: 700; + line-height: 1; + transition: transform 200ms,background 200ms; +} +.annotationtype button:hover { + transform: translateY(-2px); +} + +.annotationtype button.lastused { + color: #373a3c; + background-color: #e6e6e6; + border-color: #adadad; +} +.annotationtype button.active { + color: #373a3c; + background-color: #dae0e5;; + border-color: #dae0e5;; +} +#annotationheader { + margin-top: 20px; + margin-left: 20px; + margin-right: 10px; +} +.shortlabel { + margin-bottom: 10px; + font-weight: bold; + display: inline-block; +} +#instructions .annotation { + display: none; + max-width: 300px; + font-size: 12px; + line-height: 1.42857em; + min-height: 2.85714em; +} +#instructions[annotation="polygon"] .annotation.polygon, +#instructions[annotation="rectangle"] .annotation.rectangle, +#instructions[annotation="square"] .annotation.square, +#instructions[annotation="ellipse"] .annotation.ellipse, +#instructions[annotation="circle"] .annotation.circle, +#instructions[annotation="point"] .annotation.point, +#instructions[annotation="line"] .annotation.line, +#instructions[annotation="none"] .annotation.none { + display: block; +} +#controls a { + color: #333; + cursor: pointer; +} +#controls .form-group.compact label { + min-width: 0px; + padding-right: 5px; +} +#controls .form-group.compact select { + padding-right: 10px; +} +#controls .form-group.compact input[type="range"] { + width: 120px; + display: inline-block; + vertical-align: middle; +} + +.entry span, .entry a { + display: inline-block; +} + +.entry { + margin-right: 10px; +} +.entry .entry-name { + margin-left: 10px; + margin-right: 10px; +} +#tinybuttons { + float: right; +} +.entry .entry-edit { + font-weight: bold; +} +.entry-remove-all { + float: right; +} +#annotationheader, .entry#sample { + display: none; +} +#editdialog { + font-size: 14px; +} +#editdialog .form-group { + margin-bottom: 0px; +} +#editdialog #edit-update { + float: right; + margin-left: 10px; +} +#editdialog label { + min-width: 105px; +} +#editdialog #edit-validation-error { + color: red; + font-weight: bold; +} +#geojson { + width: 100%; + resize: vertical; +} + +#label_input { + padding: 6px 12px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #212529; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + appearance: none; + border-radius: 4px; + transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out; +} +#label_input:hover { + background-color: rgb(235, 236, 240); +} +#label_input:focus { + color: #212529; + background-color: #fff; + border-color: #86b7fe; + outline: 0; + box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%); +} +#viewpoint-tooltip { + display: inline-block; + text-align: center; + width: 1.8ex; + height: 1.8ex; + font-size: 1.4ex; + line-height: 1.5ex; + border-radius: 1.2ex; + padding: 1px; + margin-left: 10px; + border: 1px solid black; +} +#viewpoint-tooltip:hover:after{ + border-color: white; +} +#viewpoint-tooltip { + position: relative; + z-index: 2; + cursor: pointer; +} + +/* Hide the tooltip content by default */ +#viewpoint-tooltip:before, +#viewpoint-tooltip:after { + visibility: hidden; + opacity: 0; + pointer-events: none; +} + +/* Position tooltip above the element */ +#viewpoint-tooltip:before { + position: absolute; + bottom: 150%; + left: 50%; + margin-bottom: 5px; + margin-left: -80px; + padding: 7px; + width: 500px; + border-radius: 3px; + background-color: #000; + background-color: hsla(0, 0%, 20%, 0.9); + color: #fff; + content: attr(data-tooltip); + text-align: center; + font-size: 14px; + line-height: 1.2; +} + +/* Show tooltip content on hover */ +#viewpoint-tooltip:hover:before, +#viewpoint-tooltip:hover:after { + visibility: visible; + opacity: 1; +} +.form-actions { + z-index: 1000; } \ No newline at end of file diff --git a/geotrek/common/templates/common/hdviewpoint_annotation_form.html b/geotrek/common/templates/common/hdviewpoint_annotation_form.html new file mode 100644 index 0000000000..6a75ed7343 --- /dev/null +++ b/geotrek/common/templates/common/hdviewpoint_annotation_form.html @@ -0,0 +1,47 @@ +{% extends "mapentity/mapentity_form.html" %} +{% load i18n static crispy_forms_tags %} +{% block title %}{{ object.title }} {{ block.super }}{% endblock title %} +{% block mainpanel %} +
+
+
+
+ +
+ {% comment %}
{% endcomment %} +
+
{% trans "Created annotations" %}
+ +
+
+
+
+ Sample + + + + + +
+
+
+
+
+ {% block mainform %} + {% crispy form form.helper %} + {% endblock mainform %} +
+ {% url 'common:hdviewpoint-drf-detail' object.pk as base_tile_url %} + {{object.annotations|json_script:"geojson_annotations"}} +
+ + + +{% endblock mainpanel %} + +{% block extrabody %} + {{ block.super}} + +{% endblock extrabody %} diff --git a/geotrek/common/templates/common/hdviewpoint_detail.html b/geotrek/common/templates/common/hdviewpoint_detail.html new file mode 100644 index 0000000000..bd7a8d180b --- /dev/null +++ b/geotrek/common/templates/common/hdviewpoint_detail.html @@ -0,0 +1,160 @@ +{% extends "mapentity/base_site.html" %} +{% load i18n static mapentity_tags leaflet_tags %} + +{% block head %} + {% block title %}{{ object }} | {{ block.super }}{% endblock title %} + {{ block.super }} +{% endblock head %} +{% block mainpanel %} +
+

{{ object }}

+
+
+ +
+ +
+ {% block detailspanel %} + +

{% trans "Attributes" %}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ object|verbose:"title" }}{% if object.title %}{{ object.title|safe }} + {% else %}{% trans "None" %}{% endif %} +
{{ object|verbose:"legend" }}{% if object.legend %}{{ object.legend|safe }} + {% else %}{% trans "None" %}{% endif %} +
{{ object|verbose:"license" }}{% if object.license %}{{ object.license|safe }} + {% else %}{% trans "None" %}{% endif %} +
{{ object|verbose:"author" }}{% if object.author %}{{ object.author|safe }} + {% else %}{% trans "None" %}{% endif %} +
{% trans "Related to" %}{% if object.content_object %}{{ object.content_object.name_display|safe }} + {% else %}{% trans "None" %}{% endif %} +
{{ object|verbose:"date_insert" }}{{ object.date_insert|date }} +
{{ object|verbose:"date_update" }}{{ object.date_update|date }} +
+ + + {% url 'common:hdviewpoint-drf-detail' object.pk as base_tile_url %} + {{object.annotations|json_script:"geojson_annotations"}} +
+ + + {% endblock detailspanel %} +
+ {% if ACTION_HISTORY_ENABLED %} +
+ + + + + + + + + + {% for logentry in logentries %} + + + + + + {% empty %} + + + + {% endfor %} + {% if logentries_hellip %} + + + + + + {% endif %} + +
{% trans "Date" %}{% trans "User" %}{% trans "Action" %}
{{ logentry.action_time }} ({{ logentry.action_time|timesince }}){{ logentry.user }}{{ logentry.action_flag_display }}{% if logentry.change_message %} - {{ logentry.change_message }}{% endif %}
{% trans "None" %}
+ {% if 'mapentity.read_logentry' in perms %} + + {% trans "Full history" %} + + {% endif %} +
+ {% endif %} +
+
+
+ +
+ +
+ {% block mappanel %} + {% if object.get_geom %} + {% include "mapentity/mapgeometry_fragment.html" with mapname="detailmap" %} + {% else %} +

{{ empty_map_message }}

+ {% endif %} + {% endblock mappanel %} +
+ +{% endblock mainpanel %} +{% block extrabody %} + {{ block.super}} + + +{% endblock %} diff --git a/geotrek/common/templates/common/hdviewpoint_detail_fragment.html b/geotrek/common/templates/common/hdviewpoint_detail_fragment.html new file mode 100644 index 0000000000..b12824a5b6 --- /dev/null +++ b/geotrek/common/templates/common/hdviewpoint_detail_fragment.html @@ -0,0 +1,72 @@ +{% load i18n mapentity_tags %} + +

{% trans "HD Views" %} + ? +

+{% if not user.profile.structure == object.structure and not perms.authent.can_bypass_structure %} +
+ {% trans "You are not allowed to modify attachments on this object, this object is not from the same structure." %} +
+{% else %} + {% if perms.common.read_hdviewpoint %} + {% if perms.common.add_hdviewpoint %} +

+ + {% trans "Add a new HD view" %} +

+ {% endif %} + + {% for viewpoint in object.view_points.all %} + {% if forloop.first %} + + + + + + + {% block extra_column_header %}{% endblock %} + {% if user.profile.structure == object.structure or perms.authent.can_bypass_structure %} + {% block actions_attachment_header %} + + {% endblock actions_attachment_header %} + {% endif %} + + {% endif %} + + + + + + + + {% if user.profile.structure == object.structure or perms.authent.can_bypass_structure and perms.common.change_hdviewpoint or perms.common.delete_hdviewpoint%} + + {% endif %} + {% endif %} + + {% endfor %} +
{% trans "File" %}{% trans "Title" %}{% trans "Legend" %}{% trans "Author" %}{% trans "Update date" %}{% trans "Actions" %}
+ + {{ viewpoint.title|safe }} + + {{ viewpoint.title }}{{ viewpoint.legend }}

{{ viewpoint.author }}

{{ viewpoint.license.label }}

{{ viewpoint.date_update }} + + {% trans "Details" %} + + {% if perms.common.change_hdviewpoint %} +   + + {% trans "Update" %} + + {% endif %} + {% if perms.common.delete_hdviewpoint %} +   + + {% trans "Delete" %} + +
+ {% else %} + {% trans "You are not allowed to see HD views." %} + {% endif %} +{% endif %} diff --git a/geotrek/common/templates/common/hdviewpoint_form.html b/geotrek/common/templates/common/hdviewpoint_form.html new file mode 100644 index 0000000000..9d688169b8 --- /dev/null +++ b/geotrek/common/templates/common/hdviewpoint_form.html @@ -0,0 +1 @@ +{% extends "mapentity/mapentity_form.html" %} diff --git a/geotrek/common/templates/common/sql/post_90_defaults.sql b/geotrek/common/templates/common/sql/post_90_defaults.sql index f552ffc0b6..37a9b72dc9 100644 --- a/geotrek/common/templates/common/sql/post_90_defaults.sql +++ b/geotrek/common/templates/common/sql/post_90_defaults.sql @@ -82,4 +82,20 @@ ALTER TABLE common_label ALTER COLUMN date_update SET DEFAULT now(); -- TargetPortal ALTER TABLE common_targetportal ALTER COLUMN date_insert SET DEFAULT now(); -ALTER TABLE common_targetportal ALTER COLUMN date_update SET DEFAULT now(); \ No newline at end of file +ALTER TABLE common_targetportal ALTER COLUMN date_update SET DEFAULT now(); + +-- HDViewPoint +-------- +-- picture +-- geom +-- content_type +-- content_object +-- object_id +-- title +-- license +ALTER TABLE common_hdviewpoint ALTER COLUMN legend SET DEFAULT ''; +ALTER TABLE common_hdviewpoint ALTER COLUMN author SET DEFAULT ''; +ALTER TABLE common_hdviewpoint ALTER COLUMN uuid SET DEFAULT gen_random_uuid(); +ALTER TABLE common_hdviewpoint ALTER COLUMN annotations SET DEFAULT '{}'::jsonb; +ALTER TABLE common_hdviewpoint ALTER COLUMN date_insert SET DEFAULT now(); +ALTER TABLE common_hdviewpoint ALTER COLUMN date_update SET DEFAULT now(); diff --git a/geotrek/common/templatetags/geotrek_tags.py b/geotrek/common/templatetags/geotrek_tags.py index 953567b8c0..075bcaa2b8 100644 --- a/geotrek/common/templatetags/geotrek_tags.py +++ b/geotrek/common/templatetags/geotrek_tags.py @@ -12,6 +12,11 @@ def is_photos_accessibilities_enabled(): return settings.ACCESSIBILITY_ATTACHMENTS_ENABLED +@register.simple_tag +def are_hdviews_enabled(): + return settings.ENABLE_HD_VIEWS + + @register.simple_tag def settings_value(name): return getattr(settings, name, "") diff --git a/geotrek/common/tests/data/empty_image.jpg b/geotrek/common/tests/data/empty_image.jpg new file mode 100644 index 0000000000..a4415a060e Binary files /dev/null and b/geotrek/common/tests/data/empty_image.jpg differ diff --git a/geotrek/common/tests/factories.py b/geotrek/common/tests/factories.py index 70b6ec66fc..23b6c2b488 100644 --- a/geotrek/common/tests/factories.py +++ b/geotrek/common/tests/factories.py @@ -141,3 +141,53 @@ class Meta: creator = factory.SubFactory(UserFactory) title = factory.Sequence("Title {0}".format) legend = factory.Sequence("Legend {0}".format) + + +class HDViewPointFactory(factory.django.DjangoModelFactory): + class Meta: + model = models.HDViewPoint + picture = get_dummy_uploaded_image() + title = "A title" + author = "An author" + legend = "Something" + geom = "SRID=2154;POINT(0 0)" + annotations = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 7997.087502861313, + 6997.090981210413 + ], + [ + 7997.087502861313, + 6456.7472299480705 + ], + [ + 8631.090837675794, + 6456.7472299480705 + ], + [ + 8631.090837675794, + 6997.090981210413 + ], + [ + 7997.087502861313, + 6997.090981210413 + ] + ] + ] + }, + "properties": { + "annotationType": "ellipse", + "name": "Ellipse 1", + "annotationId": 1 + } + } + ] + } diff --git a/geotrek/common/tests/test_admin.py b/geotrek/common/tests/test_admin.py index dfb14d8354..8b72828a96 100644 --- a/geotrek/common/tests/test_admin.py +++ b/geotrek/common/tests/test_admin.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory from geotrek.common.models import Attachment, FileType, Theme -from geotrek.common.tests.factories import AttachmentFactory, ThemeFactory +from geotrek.common.tests.factories import AttachmentFactory, HDViewPointFactory, ThemeFactory from geotrek.common.utils.testdata import get_dummy_uploaded_image from geotrek.trekking.models import DifficultyLevel, POI, Trek from geotrek.trekking.tests.factories import DifficultyLevelFactory, POIFactory, TrekFactory @@ -164,3 +164,21 @@ def test_merge_actions_long_name(self): self.assertEqual(self.trek.themes.first().label, "*********************************************************************************************" "******************************* ...") + + +class HDViewPointAdminTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = SuperUserFactory() + cls.trek = TrekFactory() + cls.vp = HDViewPointFactory(content_object=cls.trek) + + def setUp(self): + self.client.force_login(self.user) + + def test_changelist_hdviewpoint(self): + list_url = reverse('admin:common_hdviewpoint_changelist') + response = self.client.get(list_url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, f'{str(self.trek)}') + self.assertContains(response, f'{self.vp.title}') diff --git a/geotrek/common/tests/test_forms.py b/geotrek/common/tests/test_forms.py new file mode 100644 index 0000000000..eed68fd074 --- /dev/null +++ b/geotrek/common/tests/test_forms.py @@ -0,0 +1,20 @@ +import json +from django.test import TestCase +from geotrek.common.forms import HDViewPointAnnotationForm +from geotrek.common.tests.factories import HDViewPointFactory +from geotrek.trekking.tests.factories import TrekFactory + + +class HDViewPointAnnotateFormTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.vp = HDViewPointFactory(content_object=TrekFactory()) + + def test_annotation_form(self): + geojson = '{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "Point", "coordinates": [7903.518111498841, 3618.542606516288]}, "properties": {"name": "Test point", "annotationId": 13, "annotationType": "point"}}]}' + data = { + "annotations": geojson + } + form = HDViewPointAnnotationForm(instance=self.vp, data=data) + form.save() + self.assertEqual(self.vp.annotations, json.loads(geojson)) diff --git a/geotrek/common/tests/test_models.py b/geotrek/common/tests/test_models.py index 2ebe1cb00c..d3b1fd4416 100644 --- a/geotrek/common/tests/test_models.py +++ b/geotrek/common/tests/test_models.py @@ -1,9 +1,13 @@ -from geotrek.authent.models import default_structure -from geotrek.common.tests.factories import LabelFactory, OrganismFactory -from geotrek.common.models import Theme +import os + from django.core.files import File from django.test import TestCase -import os + +from geotrek.authent.models import default_structure +from geotrek.authent.tests.factories import StructureFactory, UserProfileFactory, UserFactory +from geotrek.common.models import Theme +from geotrek.common.tests.factories import (HDViewPointFactory, LabelFactory, OrganismFactory) +from geotrek.trekking.tests.factories import TrekFactory class ThemeModelTest(TestCase): @@ -49,3 +53,27 @@ def test_str_with_structure(self): def test_str_without_structure(self): self.assertEqual(f"{self.organism_without_structure}", self.organism_without_structure.organism) + + +class HDViewPointTestCase(TestCase): + @classmethod + def setUpTestData(cls): + structure = StructureFactory(name="MyStructure") + cls.trek = TrekFactory(structure=structure) + cls.vp = HDViewPointFactory(content_object=cls.trek, title='Panorama') + cls.user = UserFactory() + UserProfileFactory(structure=structure, user=cls.user) + + def test_thumbnail_url(self): + self.assertEqual( + self.vp.thumbnail_url, f"/api/hdviewpoint/drf/hdviewpoints/{self.vp.pk}/data/thumbnail.png" + ) + + def test_tiles_url(self): + self.assertEqual( + self.vp.get_generic_picture_tile_url(), f"/api/hdviewpoint/drf/hdviewpoints/{self.vp.pk}/tiles/{{z}}/{{x}}/{{y}}.png?source=vips" + ) + + def test_properties(self): + self.assertEqual(str(self.vp), 'Panorama') + self.assertIn('admin/', self.vp.get_list_url()) diff --git a/geotrek/common/tests/test_serializers.py b/geotrek/common/tests/test_serializers.py new file mode 100644 index 0000000000..a91d6b1649 --- /dev/null +++ b/geotrek/common/tests/test_serializers.py @@ -0,0 +1,21 @@ + +from django.test import TestCase + +from geotrek.common.serializers import HDViewPointGeoJSONSerializer +from geotrek.trekking.tests.factories import TrekFactory + +from .factories import HDViewPointFactory + + +class HDViewPointSerializerTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.trek = TrekFactory() + cls.vp = HDViewPointFactory(content_object=cls.trek) + + def test_geojson_serializer(self): + serializer = HDViewPointGeoJSONSerializer(instance=self.vp) + coords = serializer.data.get('geometry').get('coordinates') + geom_transformed = self.vp.geom.transform(4326, clone=True) + self.assertAlmostEqual(coords[0], geom_transformed.x) + self.assertAlmostEqual(coords[1], geom_transformed.y) diff --git a/geotrek/common/tests/test_signals.py b/geotrek/common/tests/test_signals.py index 11e348d053..31850b535b 100644 --- a/geotrek/common/tests/test_signals.py +++ b/geotrek/common/tests/test_signals.py @@ -1,7 +1,7 @@ from django.test import TestCase from freezegun import freeze_time -from geotrek.common.tests.factories import OrganismFactory, AttachmentFactory, AttachmentAccessibilityFactory +from geotrek.common.tests.factories import HDViewPointFactory, OrganismFactory, AttachmentFactory, AttachmentAccessibilityFactory class CommonSignalsTestCase(TestCase): @@ -63,3 +63,22 @@ def test_date_update_when_attachment_accessibility_deleted(self): self.object.refresh_from_db() # object date_update has been updated with current datetime self.assertEqual(self.object.date_update.isoformat(), "2022-07-04T15:00:00+00:00") + + def test_date_update_when_hdviewpoint_updated(self): + """ Object date_update updated when HD view point updated """ + # add attachment + hdviewpoint = HDViewPointFactory(content_object=self.object) + with freeze_time("2022-07-04T16:00:00+00:00"): + hdviewpoint.save() + self.object.refresh_from_db() + # object date_update has been updated with current datetime + self.assertEqual(self.object.date_update.isoformat(), "2022-07-04T16:00:00+00:00") + + def test_date_update_when_hdviewpoint_deleted(self): + """ Object date_update updated when HD view point deleted """ + hdviewpoint = HDViewPointFactory(content_object=self.object) + with freeze_time("2022-07-04T17:00:00+00:00"): + hdviewpoint.delete() + self.object.refresh_from_db() + # object date_update has been updated with current datetime + self.assertEqual(self.object.date_update.isoformat(), "2022-07-04T17:00:00+00:00") diff --git a/geotrek/common/tests/test_views.py b/geotrek/common/tests/test_views.py index 09eb75682e..5a63bb1968 100644 --- a/geotrek/common/tests/test_views.py +++ b/geotrek/common/tests/test_views.py @@ -6,23 +6,30 @@ from unittest import mock from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth.models import Permission, User +from django.contrib.contenttypes.models import ContentType +from django.contrib.gis.geos import Point +from django.core.files import File from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse -from mapentity.tests.factories import UserFactory, SuperUserFactory +from geotrek.common.views import HDViewPointAPIViewSet +from mapentity.tests.factories import SuperUserFactory, UserFactory from mapentity.views.generic import MapEntityList +import geotrek.trekking.parsers # noqa +from geotrek.common.forms import HDViewPointAnnotationForm from geotrek.common.mixins.views import CustomColumnsMixin -from geotrek.common.models import FileType +from geotrek.common.models import FileType, HDViewPoint from geotrek.common.parsers import Parser -from geotrek.common.tasks import launch_sync_rando, import_datas -from geotrek.common.tests.factories import TargetPortalFactory +from geotrek.common.tasks import import_datas, launch_sync_rando +from geotrek.common.tests.factories import (HDViewPointFactory, LicenseFactory, + TargetPortalFactory) +from geotrek.common.utils.testdata import get_dummy_uploaded_image from geotrek.core.models import Path from geotrek.trekking.models import Trek from geotrek.trekking.tests.factories import TrekFactory -import geotrek.trekking.parsers # noqa class DocumentPublicPortalTest(TestCase): @@ -395,3 +402,144 @@ def test_launch_sync_rando_no_rando_root(self, mocked_stdout, command): def tearDown(self): if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando')): shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando')) + + +class HDViewPointViewTest(TestCase): + @classmethod + def setUpTestData(cls): + # Create objects + cls.trek = TrekFactory(published=False) + cls.license = LicenseFactory() + # Create user with proper permissions + cls.user_perm = UserFactory.create() + add_perm = Permission.objects.get(codename="add_hdviewpoint") + read_perm = Permission.objects.get(codename="read_hdviewpoint") + update_perm = Permission.objects.get(codename="change_hdviewpoint") + delete_perm = Permission.objects.get(codename="delete_hdviewpoint") + cls.user_perm.user_permissions.add(add_perm, read_perm, update_perm, delete_perm) + # Prepare access to test image + cls.directory = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data') + cls.files = [f for f in os.listdir(cls.directory)] + + def test_crud_view(self): + """ + Test CRUD rights and views for HD View Point object + """ + self.client.force_login(user=self.user_perm) + # Test create view + response = self.client.get('%s?object_id=%s&content_type=%s' % (HDViewPoint.get_add_url(), + self.trek.pk, + ContentType.objects.get_for_model(Trek).pk + ) + ) + self.assertEqual(response.status_code, 200) + img = get_dummy_uploaded_image() + data = { + 'picture': img, + 'title_en': "Un titre", + 'author': "Someone", + 'legend_en': "Something", + 'geom': "SRID=2154;POINT(0 0)", + "license": self.license.pk + } + response = self.client.post('%s?object_id=%s&content_type=%s' % (HDViewPoint.get_add_url(), + self.trek.pk, + ContentType.objects.get_for_model(Trek).pk + ), + data) + vp = HDViewPoint.objects.first() + self.assertEqual(response.status_code, 302) + self.assertEqual(self.trek, vp.content_object) + self.assertIn("dummy_img", vp.picture.name) + self.assertEqual(vp.title, "Un titre") + self.assertEqual(vp.author, "Someone") + self.assertEqual(vp.legend, "Something") + self.assertEqual(vp.license, self.license) + self.assertEqual(vp.geom, Point((0, 0), srid=2154)) + # TODO assert annotations + + # Test Update view + response = self.client.get(vp.get_update_url()) + self.assertEqual(response.status_code, 200) + img = get_dummy_uploaded_image() + data = { + 'picture': img, + 'title_en': "Un titre", + 'author_en': "Someone", + 'legend_en': "Something else", + 'geom': "SRID=2154;POINT(0 0)" + } + response = self.client.post(vp.get_update_url(), data) + self.assertRedirects(response, f"/hdviewpoint/{vp.pk}/", status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True) + vp = HDViewPoint.objects.first() + self.assertEqual(vp.legend, "Something else") + self.assertEqual(vp.license, None) + + # Test detail view + response = self.client.get(vp.get_detail_url()) + self.assertIn(b"Un titre", response.content) + + # Test delete view + vp = HDViewPoint.objects.first() + response = self.client.post(vp.get_delete_url(), {}, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(HDViewPoint.objects.count(), 0) + + def test_tiles_view(self): + """ + Test access rights for HD View Point tile endpoint + """ + # Create HD View point with a dummy image + self.path = os.path.join(self.directory, 'empty_image.jpg') + self.assertEqual(os.path.getsize(self.path), 18438) + viewpoint = HDViewPointFactory.create( + object_id=self.trek.pk, + content_type=ContentType.objects.get_for_model(Trek) + ) + with open(self.path, 'rb') as picto_file: + viewpoint.picture = File(picto_file, name="empty_image.jpg") + viewpoint.save() + + # Test unlogged user cannot access HD View Point tiles + tile_url = viewpoint.get_picture_tile_url(x=0, y=0, z=0) + response = self.client.get(tile_url) + self.assertEqual(response.status_code, 403) + + # Test unlogged user can access HD View Point tiles if related trek is published + self.trek.published = True + self.trek.save() + response = self.client.get(tile_url) + self.assertEqual(response.status_code, 200) + self.trek.published = False # Revert to previous state + self.trek.save() + + # Test logged in user can access HD View Point tiles + self.client.force_login(user=self.user_perm) + response = self.client.get(tile_url) + self.assertEqual(response.status_code, 200) + + def test_annotate_view(self): + """ + Test annotations form view contains form and title + """ + self.client.force_login(user=self.user_perm) + vp = HDViewPointFactory(content_object=self.trek) + response = self.client.get(vp.get_annotate_url()) + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.context['form'], HDViewPointAnnotationForm) + + def test_API_viewset(self): + vp = HDViewPointFactory(content_object=self.trek) + qs = HDViewPointAPIViewSet().get_queryset() + transformed_geom = vp.geom.transform(settings.API_SRID, clone=True) + api_geom = qs.first().api_geom + self.assertAlmostEqual(api_geom.x, transformed_geom.x) + self.assertAlmostEqual(api_geom.y, transformed_geom.y) + + def test_viewset(self): + self.client.force_login(user=self.user_perm) + vp = HDViewPointFactory(content_object=self.trek) + response = self.client.get(reverse('common:hdviewpoint-drf-detail', kwargs={'pk': vp.pk, 'format': 'geojson'})) + self.assertEqual(response.status_code, 200) + self.assertIn('id', response.json().get('properties')) + self.assertIn('title', response.json().get('properties')) diff --git a/geotrek/common/translation.py b/geotrek/common/translation.py index e40ef8af5e..8d6415121b 100644 --- a/geotrek/common/translation.py +++ b/geotrek/common/translation.py @@ -1,6 +1,6 @@ from modeltranslation.translator import translator, TranslationOptions -from geotrek.common.models import TargetPortal, Theme, Label +from geotrek.common.models import TargetPortal, Theme, Label, HDViewPoint class ThemeTO(TranslationOptions): @@ -15,6 +15,11 @@ class LabelTO(TranslationOptions): fields = ('name', 'advice') +class HDViewPointTO(TranslationOptions): + fields = ('title', 'legend') + + translator.register(Theme, ThemeTO) translator.register(TargetPortal, TargetPortalTO) translator.register(Label, LabelTO) +translator.register(HDViewPoint, HDViewPointTO) diff --git a/geotrek/common/urls.py b/geotrek/common/urls.py index bd3970f7bc..42a162678b 100644 --- a/geotrek/common/urls.py +++ b/geotrek/common/urls.py @@ -1,8 +1,10 @@ from django.urls import path, converters, register_converter -from mapentity.registry import MapEntityOptions +from geotrek.common.models import HDViewPoint +from mapentity.registry import MapEntityOptions, registry +from rest_framework.routers import DefaultRouter -from .views import (JSSettings, DocumentPublic, DocumentBookletPublic, import_view, - import_update_json, ThemeViewSet, MarkupPublic, sync_view, sync_update_json, SyncRandoRedirect, +from .views import (HDViewPointAnnotate, TiledHDViewPointViewSet, JSSettings, DocumentPublic, DocumentBookletPublic, + import_view, import_update_json, ThemeViewSet, MarkupPublic, sync_view, sync_update_json, SyncRandoRedirect, CheckExtentsView) @@ -22,8 +24,14 @@ class LangConverter(converters.StringConverter): path('commands/syncview', sync_view, name='sync_randos_view'), path('commands/statesync/', sync_update_json, name='sync_randos_state'), path('api//themes.json', ThemeViewSet.as_view({'get': 'list'}), name="themes_json"), + path('hdviewpoint/annotate/', HDViewPointAnnotate.as_view(), name="hdviewpoint_annotate"), ] +rest_router = DefaultRouter(trailing_slash=False) +rest_router.register(r'api/hdviewpoint/drf/hdviewpoints', TiledHDViewPointViewSet) +urlpatterns += registry.register(HDViewPoint, menu=False) +urlpatterns += rest_router.urls + class PublishableEntityOptions(MapEntityOptions): document_public_view = DocumentPublic diff --git a/geotrek/common/views.py b/geotrek/common/views.py index 1019e2804d..d6088b23fa 100644 --- a/geotrek/common/views.py +++ b/geotrek/common/views.py @@ -4,53 +4,70 @@ import os import re from datetime import timedelta -from zipfile import is_zipfile, ZipFile +from zipfile import ZipFile, is_zipfile import redis from django.apps import apps from django.conf import settings from django.contrib import messages -from django.contrib.admin.models import LogEntry, CHANGE -from django.contrib.auth.decorators import login_required, user_passes_test -from django.contrib.auth.decorators import permission_required +from django.contrib.admin.models import CHANGE, LogEntry +from django.contrib.auth.decorators import (login_required, + permission_required, + user_passes_test) from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.gis.db.models import Extent, GeometryField +from django.contrib.gis.db.models.functions import Transform from django.core.exceptions import PermissionDenied from django.db.models import Q from django.db.models.functions import Cast -from django.http import JsonResponse, Http404, HttpResponse, HttpResponseRedirect -from django.shortcuts import get_object_or_404, render, redirect +from django.http import (Http404, HttpResponse, HttpResponseRedirect, + JsonResponse) +from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone, translation from django.utils.decorators import method_decorator from django.utils.encoding import force_str from django.utils.translation import gettext as _ from django.views import static -from django.views.decorators.http import require_POST, require_http_methods -from django.views.generic import RedirectView, View -from django.views.generic import TemplateView +from django.views.decorators.http import require_http_methods, require_POST +from django.views.generic import RedirectView, TemplateView, UpdateView, View from django_celery_results.models import TaskResult +from django_large_image.rest import LargeImageFileDetailMixin +from geotrek.common.filters import HDViewPointFilterSet from mapentity import views as mapentity_views from mapentity.helpers import api_bbox -from mapentity.registry import registry, app_settings +from mapentity.registry import app_settings, registry +from mapentity.views import MapEntityList from paperclip import settings as settings_paperclip from paperclip.views import _handle_attachment_form -from rest_framework import permissions as rest_permissions, viewsets +from rest_framework import mixins +from rest_framework import permissions as rest_permissions +from rest_framework import viewsets from geotrek import __version__ from geotrek.celery import app as celery_app +from geotrek.common.mixins.api import APIViewSet +from geotrek.common.viewsets import GeotrekMapentityViewSet from geotrek.feedback.parsers import SuricateParser -from .forms import AttachmentAccessibilityForm, ImportDatasetForm, ImportSuricateForm, ImportDatasetFormWithFile, \ - SyncRandoForm -from .mixins.views import MetaMixin, DocumentPortalMixin, DocumentPublicMixin, BookletMixin -from .models import TargetPortal, AccessibilityAttachment, Theme -from .permissions import PublicOrReadPermMixin -from .serializers import ThemeSerializer -from .tasks import import_datas, import_datas_from_web, launch_sync_rando -from .utils import leaflet_bounds -from .utils.import_celery import create_tmp_destination, discover_available_parsers + from ..altimetry.models import Dem from ..core.models import Path +from .forms import (AttachmentAccessibilityForm, HDViewPointAnnotationForm, + HDViewPointForm, ImportDatasetForm, + ImportDatasetFormWithFile, ImportSuricateForm, + SyncRandoForm) +from .mixins.views import (BookletMixin, CompletenessMixin, + DocumentPortalMixin, DocumentPublicMixin, MetaMixin) +from .models import AccessibilityAttachment, HDViewPoint, TargetPortal, Theme +from .permissions import PublicOrReadPermMixin, RelatedPublishedPermission +from .serializers import (HDViewPointAPIGeoJSONSerializer, + HDViewPointAPISerializer, + HDViewPointGeoJSONSerializer, HDViewPointSerializer, + ThemeSerializer) +from .tasks import import_datas, import_datas_from_web, launch_sync_rando +from .utils import leaflet_bounds +from .utils.import_celery import (create_tmp_destination, + discover_available_parsers) class Meta(MetaMixin, TemplateView): @@ -316,6 +333,81 @@ def get(request, *args, **kwargs): return JsonResponse(response) +class HDViewPointList(MapEntityList): + queryset = HDViewPoint.objects.all() + filterform = HDViewPointFilterSet + columns = ['id', 'title'] + + +class HDViewPointViewSet(GeotrekMapentityViewSet): + model = HDViewPoint + serializer_class = HDViewPointSerializer + geojson_serializer_class = HDViewPointGeoJSONSerializer + mapentity_list_class = HDViewPointList + + def get_queryset(self): + qs = self.model.objects.all() + if self.format_kwarg == 'geojson': + qs = qs.only('id', 'title') + return qs + + +class HDViewPointAPIViewSet(APIViewSet): + model = HDViewPoint + serializer_class = HDViewPointAPISerializer + geojson_serializer_class = HDViewPointAPIGeoJSONSerializer + + def get_queryset(self): + return HDViewPoint.objects.annotate(api_geom=Transform("geom", settings.API_SRID)) + + +class HDViewPointDetail(CompletenessMixin, mapentity_views.MapEntityDetail, LoginRequiredMixin): + model = HDViewPoint + queryset = HDViewPoint.objects.all().select_related('content_type', 'license') + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['can_edit'] = self.get_object().content_object.same_structure(self.request.user) + return context + + +class HDViewPointCreate(mapentity_views.MapEntityCreate, LoginRequiredMixin): + model = HDViewPoint + form_class = HDViewPointForm + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['content_type'] = self.request.GET.get('content_type') + kwargs['object_id'] = self.request.GET.get('object_id') + return kwargs + + +class HDViewPointUpdate(mapentity_views.MapEntityUpdate, LoginRequiredMixin): + queryset = HDViewPoint.objects.all() + form_class = HDViewPointForm + + +class HDViewPointDelete(mapentity_views.MapEntityDelete, LoginRequiredMixin): + model = HDViewPoint + + def get_success_url(self): + return self.get_object().content_object.get_detail_url() + + +class HDViewPointAnnotate(UpdateView, LoginRequiredMixin): + model = HDViewPoint + form_class = HDViewPointAnnotationForm + template_name_suffix = '_annotation_form' + + +class TiledHDViewPointViewSet(mixins.ListModelMixin, viewsets.GenericViewSet, LargeImageFileDetailMixin): + queryset = HDViewPoint.objects.all() + serializer_class = HDViewPointAPISerializer + permission_classes = [RelatedPublishedPermission] + # for `django-large-image`: the name of the image FileField on your model + FILE_FIELD_NAME = 'picture' + + @login_required def last_list(request): last = request.session.get('last_list') # set in MapEntityList diff --git a/geotrek/core/locale/de/LC_MESSAGES/django.po b/geotrek/core/locale/de/LC_MESSAGES/django.po index c07fbadb43..4092098ccd 100644 --- a/geotrek/core/locale/de/LC_MESSAGES/django.po +++ b/geotrek/core/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-24 14:14+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/core/locale/en/LC_MESSAGES/django.po b/geotrek/core/locale/en/LC_MESSAGES/django.po index c07fbadb43..4092098ccd 100644 --- a/geotrek/core/locale/en/LC_MESSAGES/django.po +++ b/geotrek/core/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-24 14:14+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/core/locale/es/LC_MESSAGES/django.po b/geotrek/core/locale/es/LC_MESSAGES/django.po index c07fbadb43..4092098ccd 100644 --- a/geotrek/core/locale/es/LC_MESSAGES/django.po +++ b/geotrek/core/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-24 14:14+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/core/locale/fr/LC_MESSAGES/django.po b/geotrek/core/locale/fr/LC_MESSAGES/django.po index 6094451e52..47cd03c3cf 100644 --- a/geotrek/core/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/core/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-24 14:14+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-09-23 07:10+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/core/locale/nl/LC_MESSAGES/django.po b/geotrek/core/locale/nl/LC_MESSAGES/django.po index c07fbadb43..4092098ccd 100644 --- a/geotrek/core/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/core/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-24 14:14+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/diving/locale/de/LC_MESSAGES/django.po b/geotrek/diving/locale/de/LC_MESSAGES/django.po index ae620b2efc..e9ddc48f8d 100644 --- a/geotrek/diving/locale/de/LC_MESSAGES/django.po +++ b/geotrek/diving/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/diving/locale/en/LC_MESSAGES/django.po b/geotrek/diving/locale/en/LC_MESSAGES/django.po index ae620b2efc..e9ddc48f8d 100644 --- a/geotrek/diving/locale/en/LC_MESSAGES/django.po +++ b/geotrek/diving/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/diving/locale/es/LC_MESSAGES/django.po b/geotrek/diving/locale/es/LC_MESSAGES/django.po index ae620b2efc..e9ddc48f8d 100644 --- a/geotrek/diving/locale/es/LC_MESSAGES/django.po +++ b/geotrek/diving/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/diving/locale/fr/LC_MESSAGES/django.po b/geotrek/diving/locale/fr/LC_MESSAGES/django.po index 9bda29556b..14f754d467 100644 --- a/geotrek/diving/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/diving/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-04-22 06:45+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/diving/locale/nl/LC_MESSAGES/django.po b/geotrek/diving/locale/nl/LC_MESSAGES/django.po index ae620b2efc..e9ddc48f8d 100644 --- a/geotrek/diving/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/diving/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/feedback/locale/de/LC_MESSAGES/django.po b/geotrek/feedback/locale/de/LC_MESSAGES/django.po index 8d21be6711..ed60e5edc1 100644 --- a/geotrek/feedback/locale/de/LC_MESSAGES/django.po +++ b/geotrek/feedback/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/feedback/locale/en/LC_MESSAGES/django.po b/geotrek/feedback/locale/en/LC_MESSAGES/django.po index 8d21be6711..ed60e5edc1 100644 --- a/geotrek/feedback/locale/en/LC_MESSAGES/django.po +++ b/geotrek/feedback/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/feedback/locale/es/LC_MESSAGES/django.po b/geotrek/feedback/locale/es/LC_MESSAGES/django.po index 8d21be6711..ed60e5edc1 100644 --- a/geotrek/feedback/locale/es/LC_MESSAGES/django.po +++ b/geotrek/feedback/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/feedback/locale/fr/LC_MESSAGES/django.po b/geotrek/feedback/locale/fr/LC_MESSAGES/django.po index a8867191ef..a9a9f5c4cb 100644 --- a/geotrek/feedback/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/feedback/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/feedback/locale/it/LC_MESSAGES/django.po b/geotrek/feedback/locale/it/LC_MESSAGES/django.po index 8d21be6711..ed60e5edc1 100644 --- a/geotrek/feedback/locale/it/LC_MESSAGES/django.po +++ b/geotrek/feedback/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/feedback/locale/nl/LC_MESSAGES/django.po b/geotrek/feedback/locale/nl/LC_MESSAGES/django.po index 8d21be6711..ed60e5edc1 100644 --- a/geotrek/feedback/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/feedback/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/flatpages/locale/de/LC_MESSAGES/django.po b/geotrek/flatpages/locale/de/LC_MESSAGES/django.po index edba1d0716..b9f8fc6ff9 100644 --- a/geotrek/flatpages/locale/de/LC_MESSAGES/django.po +++ b/geotrek/flatpages/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/flatpages/locale/en/LC_MESSAGES/django.po b/geotrek/flatpages/locale/en/LC_MESSAGES/django.po index edba1d0716..b9f8fc6ff9 100644 --- a/geotrek/flatpages/locale/en/LC_MESSAGES/django.po +++ b/geotrek/flatpages/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/flatpages/locale/es/LC_MESSAGES/django.po b/geotrek/flatpages/locale/es/LC_MESSAGES/django.po index edba1d0716..b9f8fc6ff9 100644 --- a/geotrek/flatpages/locale/es/LC_MESSAGES/django.po +++ b/geotrek/flatpages/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/flatpages/locale/fr/LC_MESSAGES/django.po b/geotrek/flatpages/locale/fr/LC_MESSAGES/django.po index 5d042025b2..b7ead1b032 100644 --- a/geotrek/flatpages/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/flatpages/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/flatpages/locale/it/LC_MESSAGES/django.po b/geotrek/flatpages/locale/it/LC_MESSAGES/django.po index edba1d0716..b9f8fc6ff9 100644 --- a/geotrek/flatpages/locale/it/LC_MESSAGES/django.po +++ b/geotrek/flatpages/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/flatpages/locale/nl/LC_MESSAGES/django.po b/geotrek/flatpages/locale/nl/LC_MESSAGES/django.po index edba1d0716..b9f8fc6ff9 100644 --- a/geotrek/flatpages/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/flatpages/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/infrastructure/locale/de/LC_MESSAGES/django.po b/geotrek/infrastructure/locale/de/LC_MESSAGES/django.po index 07d9086d1f..8f34f483a6 100644 --- a/geotrek/infrastructure/locale/de/LC_MESSAGES/django.po +++ b/geotrek/infrastructure/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/infrastructure/locale/en/LC_MESSAGES/django.po b/geotrek/infrastructure/locale/en/LC_MESSAGES/django.po index a3c042be2d..0bfc4c79f5 100644 --- a/geotrek/infrastructure/locale/en/LC_MESSAGES/django.po +++ b/geotrek/infrastructure/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/infrastructure/locale/es/LC_MESSAGES/django.po b/geotrek/infrastructure/locale/es/LC_MESSAGES/django.po index a3c042be2d..0bfc4c79f5 100644 --- a/geotrek/infrastructure/locale/es/LC_MESSAGES/django.po +++ b/geotrek/infrastructure/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/infrastructure/locale/fr/LC_MESSAGES/django.po b/geotrek/infrastructure/locale/fr/LC_MESSAGES/django.po index 60905a0025..94f655d260 100644 --- a/geotrek/infrastructure/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/infrastructure/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-04-22 07:48+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/infrastructure/locale/nl/LC_MESSAGES/django.po b/geotrek/infrastructure/locale/nl/LC_MESSAGES/django.po index a3c042be2d..0bfc4c79f5 100644 --- a/geotrek/infrastructure/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/infrastructure/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/land/locale/de/LC_MESSAGES/django.po b/geotrek/land/locale/de/LC_MESSAGES/django.po index 47956adc76..e1ef41213b 100644 --- a/geotrek/land/locale/de/LC_MESSAGES/django.po +++ b/geotrek/land/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/land/locale/en/LC_MESSAGES/django.po b/geotrek/land/locale/en/LC_MESSAGES/django.po index 47956adc76..e1ef41213b 100644 --- a/geotrek/land/locale/en/LC_MESSAGES/django.po +++ b/geotrek/land/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/land/locale/es/LC_MESSAGES/django.po b/geotrek/land/locale/es/LC_MESSAGES/django.po index 47956adc76..e1ef41213b 100644 --- a/geotrek/land/locale/es/LC_MESSAGES/django.po +++ b/geotrek/land/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/land/locale/fr/LC_MESSAGES/django.po b/geotrek/land/locale/fr/LC_MESSAGES/django.po index 6c0230fc03..6c7ebf136e 100644 --- a/geotrek/land/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/land/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/land/locale/it/LC_MESSAGES/django.po b/geotrek/land/locale/it/LC_MESSAGES/django.po index 47956adc76..e1ef41213b 100644 --- a/geotrek/land/locale/it/LC_MESSAGES/django.po +++ b/geotrek/land/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/land/locale/nl/LC_MESSAGES/django.po b/geotrek/land/locale/nl/LC_MESSAGES/django.po index 47956adc76..e1ef41213b 100644 --- a/geotrek/land/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/land/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/locale/de/LC_MESSAGES/django.po b/geotrek/locale/de/LC_MESSAGES/django.po index 1d3ae4a664..d87004af8f 100644 --- a/geotrek/locale/de/LC_MESSAGES/django.po +++ b/geotrek/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/locale/en/LC_MESSAGES/django.po b/geotrek/locale/en/LC_MESSAGES/django.po index 1d3ae4a664..d87004af8f 100644 --- a/geotrek/locale/en/LC_MESSAGES/django.po +++ b/geotrek/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/locale/es/LC_MESSAGES/django.po b/geotrek/locale/es/LC_MESSAGES/django.po index 1d3ae4a664..d87004af8f 100644 --- a/geotrek/locale/es/LC_MESSAGES/django.po +++ b/geotrek/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/locale/fr/LC_MESSAGES/django.po b/geotrek/locale/fr/LC_MESSAGES/django.po index 77d19341a8..b73d05123a 100644 --- a/geotrek/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-09-23 07:10+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/locale/nl/LC_MESSAGES/django.po b/geotrek/locale/nl/LC_MESSAGES/django.po index 1d3ae4a664..d87004af8f 100644 --- a/geotrek/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/maintenance/locale/de/LC_MESSAGES/django.po b/geotrek/maintenance/locale/de/LC_MESSAGES/django.po index 598bd92c2a..434260d4a0 100644 --- a/geotrek/maintenance/locale/de/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/maintenance/locale/en/LC_MESSAGES/django.po b/geotrek/maintenance/locale/en/LC_MESSAGES/django.po index 598bd92c2a..434260d4a0 100644 --- a/geotrek/maintenance/locale/en/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/maintenance/locale/es/LC_MESSAGES/django.po b/geotrek/maintenance/locale/es/LC_MESSAGES/django.po index 598bd92c2a..434260d4a0 100644 --- a/geotrek/maintenance/locale/es/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po b/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po index a6c8ae48e3..1875df9846 100644 --- a/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/maintenance/locale/it/LC_MESSAGES/django.po b/geotrek/maintenance/locale/it/LC_MESSAGES/django.po index 598bd92c2a..434260d4a0 100644 --- a/geotrek/maintenance/locale/it/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po b/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po index 598bd92c2a..434260d4a0 100644 --- a/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/outdoor/locale/de/LC_MESSAGES/django.po b/geotrek/outdoor/locale/de/LC_MESSAGES/django.po index 8314a8f8b7..f3dc89fd9c 100644 --- a/geotrek/outdoor/locale/de/LC_MESSAGES/django.po +++ b/geotrek/outdoor/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -355,6 +355,9 @@ msgstr "" msgid "Add a brother site" msgstr "" +msgid "Attached files" +msgstr "" + msgid "Parents" msgstr "" diff --git a/geotrek/outdoor/locale/en/LC_MESSAGES/django.po b/geotrek/outdoor/locale/en/LC_MESSAGES/django.po index 8314a8f8b7..f3dc89fd9c 100644 --- a/geotrek/outdoor/locale/en/LC_MESSAGES/django.po +++ b/geotrek/outdoor/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -355,6 +355,9 @@ msgstr "" msgid "Add a brother site" msgstr "" +msgid "Attached files" +msgstr "" + msgid "Parents" msgstr "" diff --git a/geotrek/outdoor/locale/es/LC_MESSAGES/django.po b/geotrek/outdoor/locale/es/LC_MESSAGES/django.po index 8314a8f8b7..f3dc89fd9c 100644 --- a/geotrek/outdoor/locale/es/LC_MESSAGES/django.po +++ b/geotrek/outdoor/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -355,6 +355,9 @@ msgstr "" msgid "Add a brother site" msgstr "" +msgid "Attached files" +msgstr "" + msgid "Parents" msgstr "" diff --git a/geotrek/outdoor/locale/fr/LC_MESSAGES/django.po b/geotrek/outdoor/locale/fr/LC_MESSAGES/django.po index 2081997d90..f8b4a04d0e 100644 --- a/geotrek/outdoor/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/outdoor/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -357,6 +357,9 @@ msgstr "Ajouter site enfant" msgid "Add a brother site" msgstr "Ajouter un site frère" +msgid "Attached files" +msgstr "" + msgid "Parents" msgstr "Parents" diff --git a/geotrek/outdoor/locale/it/LC_MESSAGES/django.po b/geotrek/outdoor/locale/it/LC_MESSAGES/django.po index 8314a8f8b7..f3dc89fd9c 100644 --- a/geotrek/outdoor/locale/it/LC_MESSAGES/django.po +++ b/geotrek/outdoor/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -355,6 +355,9 @@ msgstr "" msgid "Add a brother site" msgstr "" +msgid "Attached files" +msgstr "" + msgid "Parents" msgstr "" diff --git a/geotrek/outdoor/locale/nl/LC_MESSAGES/django.po b/geotrek/outdoor/locale/nl/LC_MESSAGES/django.po index 8314a8f8b7..f3dc89fd9c 100644 --- a/geotrek/outdoor/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/outdoor/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -355,6 +355,9 @@ msgstr "" msgid "Add a brother site" msgstr "" +msgid "Attached files" +msgstr "" + msgid "Parents" msgstr "" diff --git a/geotrek/outdoor/models.py b/geotrek/outdoor/models.py index 6befa5ddc4..dbe86ab2ab 100644 --- a/geotrek/outdoor/models.py +++ b/geotrek/outdoor/models.py @@ -1,6 +1,7 @@ import uuid from django.conf import settings from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes.fields import GenericRelation from django.contrib.gis.db import models from django.contrib.gis.measure import D from django.contrib.postgres.indexes import GistIndex @@ -173,6 +174,7 @@ class Site(ZoningPropertiesMixin, AddPropertyMixin, PicturesMixin, PublishableMi provider = models.CharField(verbose_name=_("Provider"), db_index=True, max_length=1024, blank=True) managers = models.ManyToManyField(Organism, verbose_name=_("Managers"), blank=True) uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) + view_points = GenericRelation('common.HDViewPoint', related_query_name='site') check_structure_in_forms = False diff --git a/geotrek/outdoor/templates/outdoor/site_detail.html b/geotrek/outdoor/templates/outdoor/site_detail.html index 2983dbddce..6a2f0faaf9 100644 --- a/geotrek/outdoor/templates/outdoor/site_detail.html +++ b/geotrek/outdoor/templates/outdoor/site_detail.html @@ -1,5 +1,5 @@ {% extends "common/common_detail.html" %} -{% load i18n l10n static thumbnail %} +{% load i18n l10n static thumbnail geotrek_tags %} {% block download %} {{ block.super }} @@ -14,3 +14,22 @@ {% include "common/publishable_completeness_fragment.html" %} {{ block.super }} {% endblock detailspanel %} + +{% block attachments_extra_tab_nav %} + {% with attachments_count=object.attachments.count|add:object.view_points.count %} + + {% endwith %%} +{% endblock %} + +{% block attachmentspanel %} + {{ block.super }} + {% are_hdviews_enabled as are_hdviews_enabled %} + {% if are_hdviews_enabled %} +
+
+ {% include "common/hdviewpoint_detail_fragment.html" %} + {% endif %} +{% endblock attachmentspanel %} diff --git a/geotrek/outdoor/views.py b/geotrek/outdoor/views.py index dab3cfa8db..7f36c3ec4c 100644 --- a/geotrek/outdoor/views.py +++ b/geotrek/outdoor/views.py @@ -1,6 +1,7 @@ from django.conf import settings from django.contrib.gis.db.models.functions import Transform -from django.db.models import Q +from django.db.models import Q, Prefetch +from geotrek.common.models import HDViewPoint from mapentity.helpers import alphabet_enumeration from mapentity.views import (MapEntityList, MapEntityDetail, MapEntityDocument, MapEntityCreate, MapEntityUpdate, MapEntityDelete, MapEntityFormat) @@ -27,7 +28,10 @@ class SiteList(CustomColumnsMixin, MapEntityList): class SiteDetail(CompletenessMixin, MapEntityDetail): - queryset = Site.objects.all() + queryset = Site.objects.all().prefetch_related( + Prefetch('view_points', + queryset=HDViewPoint.objects.select_related('content_type', 'license')) + ) def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) diff --git a/geotrek/sensitivity/locale/de/LC_MESSAGES/django.po b/geotrek/sensitivity/locale/de/LC_MESSAGES/django.po index ad595a8157..1225ef8ccc 100644 --- a/geotrek/sensitivity/locale/de/LC_MESSAGES/django.po +++ b/geotrek/sensitivity/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/sensitivity/locale/en/LC_MESSAGES/django.po b/geotrek/sensitivity/locale/en/LC_MESSAGES/django.po index ad595a8157..1225ef8ccc 100644 --- a/geotrek/sensitivity/locale/en/LC_MESSAGES/django.po +++ b/geotrek/sensitivity/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/sensitivity/locale/es/LC_MESSAGES/django.po b/geotrek/sensitivity/locale/es/LC_MESSAGES/django.po index ad595a8157..1225ef8ccc 100644 --- a/geotrek/sensitivity/locale/es/LC_MESSAGES/django.po +++ b/geotrek/sensitivity/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/sensitivity/locale/fr/LC_MESSAGES/django.po b/geotrek/sensitivity/locale/fr/LC_MESSAGES/django.po index 7829b9eb73..d1647ed05d 100644 --- a/geotrek/sensitivity/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/sensitivity/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-04-22 07:48+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/sensitivity/locale/nl/LC_MESSAGES/django.po b/geotrek/sensitivity/locale/nl/LC_MESSAGES/django.po index ad595a8157..1225ef8ccc 100644 --- a/geotrek/sensitivity/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/sensitivity/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/settings/base.py b/geotrek/settings/base.py index 8682ff8fe1..2aee4ea91a 100644 --- a/geotrek/settings/base.py +++ b/geotrek/settings/base.py @@ -292,6 +292,7 @@ def api_bbox(bbox, buffer): 'rest_framework_gis', 'embed_video', 'django_celery_results', + 'django_large_image', 'colorfield', 'mptt', ) @@ -833,6 +834,8 @@ def api_bbox(bbox, buffer): ALLOW_PATH_DELETION_TOPOLOGY = True +ENABLE_HD_VIEWS = True + # Override with prod/dev/tests/tests_nds settings ENV = os.getenv('ENV', 'prod') assert ENV in ('prod', 'dev', 'tests', 'tests_nds') diff --git a/geotrek/signage/locale/de/LC_MESSAGES/django.po b/geotrek/signage/locale/de/LC_MESSAGES/django.po index 8e84bd2c7e..f718085c1e 100644 --- a/geotrek/signage/locale/de/LC_MESSAGES/django.po +++ b/geotrek/signage/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/signage/locale/en/LC_MESSAGES/django.po b/geotrek/signage/locale/en/LC_MESSAGES/django.po index 8e84bd2c7e..f718085c1e 100644 --- a/geotrek/signage/locale/en/LC_MESSAGES/django.po +++ b/geotrek/signage/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/signage/locale/es/LC_MESSAGES/django.po b/geotrek/signage/locale/es/LC_MESSAGES/django.po index 8e84bd2c7e..f718085c1e 100644 --- a/geotrek/signage/locale/es/LC_MESSAGES/django.po +++ b/geotrek/signage/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/signage/locale/fr/LC_MESSAGES/django.po b/geotrek/signage/locale/fr/LC_MESSAGES/django.po index 5c8f7723d7..6d9824600d 100644 --- a/geotrek/signage/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/signage/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2015-09-25 17:37+0100\n" "Last-Translator: \n" "Language-Team: \n" diff --git a/geotrek/signage/locale/it/LC_MESSAGES/django.po b/geotrek/signage/locale/it/LC_MESSAGES/django.po index 8e84bd2c7e..f718085c1e 100644 --- a/geotrek/signage/locale/it/LC_MESSAGES/django.po +++ b/geotrek/signage/locale/it/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/signage/locale/nl/LC_MESSAGES/django.po b/geotrek/signage/locale/nl/LC_MESSAGES/django.po index 8e84bd2c7e..f718085c1e 100644 --- a/geotrek/signage/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/signage/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/static/images/hdviewpoint-16.png b/geotrek/static/images/hdviewpoint-16.png new file mode 100644 index 0000000000..1cb8cfe7a6 Binary files /dev/null and b/geotrek/static/images/hdviewpoint-16.png differ diff --git a/geotrek/static/images/hdviewpoint-96.png b/geotrek/static/images/hdviewpoint-96.png new file mode 100644 index 0000000000..684110935f Binary files /dev/null and b/geotrek/static/images/hdviewpoint-96.png differ diff --git a/geotrek/static/images/hdviewpoint.png b/geotrek/static/images/hdviewpoint.png new file mode 100644 index 0000000000..1f880edbd9 Binary files /dev/null and b/geotrek/static/images/hdviewpoint.png differ diff --git a/geotrek/tourism/locale/de/LC_MESSAGES/django.po b/geotrek/tourism/locale/de/LC_MESSAGES/django.po index 511d585564..ebc1e1ced2 100644 --- a/geotrek/tourism/locale/de/LC_MESSAGES/django.po +++ b/geotrek/tourism/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2015-10-22 14:33+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -328,7 +328,16 @@ msgstr "" msgid "Boolean indicating if Event is cancelled" msgstr "" -msgid "Select a place to auto-locate event on map" +msgid "Preparation duration" +msgstr "" + +msgid "In hours (1.5 = 1 h 30, 24 = 1 day, 48 = 2 days)" +msgstr "" + +msgid "Intervention duration" +msgstr "" + +msgid "Select a place in the list or locate the event directly on the map" msgstr "" msgid "Number of participants" diff --git a/geotrek/tourism/locale/en/LC_MESSAGES/django.po b/geotrek/tourism/locale/en/LC_MESSAGES/django.po index 2f3abbd20c..b08e43f80d 100644 --- a/geotrek/tourism/locale/en/LC_MESSAGES/django.po +++ b/geotrek/tourism/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -327,6 +327,15 @@ msgstr "" msgid "Boolean indicating if Event is cancelled" msgstr "" +msgid "Preparation duration" +msgstr "" + +msgid "In hours (1.5 = 1 h 30, 24 = 1 day, 48 = 2 days)" +msgstr "" + +msgid "Intervention duration" +msgstr "" + msgid "Select a place in the list or locate the event directly on the map" msgstr "" @@ -438,9 +447,3 @@ msgstr "" msgid "Invalid geometry value." msgstr "" - -msgid "Preparation duration" -msgstr "Preparation duration" - -msgid "Intervention duration" -msgstr "Intervention duration" \ No newline at end of file diff --git a/geotrek/tourism/locale/es/LC_MESSAGES/django.po b/geotrek/tourism/locale/es/LC_MESSAGES/django.po index e227ef9d9e..b08e43f80d 100644 --- a/geotrek/tourism/locale/es/LC_MESSAGES/django.po +++ b/geotrek/tourism/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -327,7 +327,16 @@ msgstr "" msgid "Boolean indicating if Event is cancelled" msgstr "" -msgid "Select a place to auto-locate event on map" +msgid "Preparation duration" +msgstr "" + +msgid "In hours (1.5 = 1 h 30, 24 = 1 day, 48 = 2 days)" +msgstr "" + +msgid "Intervention duration" +msgstr "" + +msgid "Select a place in the list or locate the event directly on the map" msgstr "" msgid "Number of participants" diff --git a/geotrek/tourism/locale/fr/LC_MESSAGES/django.po b/geotrek/tourism/locale/fr/LC_MESSAGES/django.po index 04b3bbad05..6f07eef2d5 100644 --- a/geotrek/tourism/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/tourism/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: 2020-04-22 07:36+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French \n" "Language-Team: LANGUAGE \n" @@ -327,7 +327,16 @@ msgstr "" msgid "Boolean indicating if Event is cancelled" msgstr "" -msgid "Select a place to auto-locate event on map" +msgid "Preparation duration" +msgstr "" + +msgid "In hours (1.5 = 1 h 30, 24 = 1 day, 48 = 2 days)" +msgstr "" + +msgid "Intervention duration" +msgstr "" + +msgid "Select a place in the list or locate the event directly on the map" msgstr "" msgid "Number of participants" @@ -353,6 +362,12 @@ msgstr "touristic-event" msgid "Display order" msgstr "" +msgid "Participant category" +msgstr "" + +msgid "Participant categories" +msgstr "" + msgid "Published touristic events" msgstr "Pubblicato eventi turistici" diff --git a/geotrek/tourism/locale/nl/LC_MESSAGES/django.po b/geotrek/tourism/locale/nl/LC_MESSAGES/django.po index e227ef9d9e..b08e43f80d 100644 --- a/geotrek/tourism/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/tourism/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -327,7 +327,16 @@ msgstr "" msgid "Boolean indicating if Event is cancelled" msgstr "" -msgid "Select a place to auto-locate event on map" +msgid "Preparation duration" +msgstr "" + +msgid "In hours (1.5 = 1 h 30, 24 = 1 day, 48 = 2 days)" +msgstr "" + +msgid "Intervention duration" +msgstr "" + +msgid "Select a place in the list or locate the event directly on the map" msgstr "" msgid "Number of participants" diff --git a/geotrek/trekking/locale/de/LC_MESSAGES/django.po b/geotrek/trekking/locale/de/LC_MESSAGES/django.po index 4189a201ea..59c6f81ea7 100644 --- a/geotrek/trekking/locale/de/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/de/LC_MESSAGES/django.po @@ -535,6 +535,9 @@ msgstr "Roaming" msgid "Hike" msgstr "Wander" +msgid "Attached files" +msgstr "" + msgid "Attributes" msgstr "Informationen" diff --git a/geotrek/trekking/locale/en/LC_MESSAGES/django.po b/geotrek/trekking/locale/en/LC_MESSAGES/django.po index fff88115dc..391feca8da 100644 --- a/geotrek/trekking/locale/en/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/en/LC_MESSAGES/django.po @@ -520,6 +520,9 @@ msgstr "" msgid "Hike" msgstr "" +msgid "Attached files" +msgstr "" + msgid "Attributes" msgstr "" diff --git a/geotrek/trekking/locale/es/LC_MESSAGES/django.po b/geotrek/trekking/locale/es/LC_MESSAGES/django.po index d18e72daa8..821f67a97c 100644 --- a/geotrek/trekking/locale/es/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/es/LC_MESSAGES/django.po @@ -520,6 +520,9 @@ msgstr "" msgid "Hike" msgstr "Caminata" +msgid "Attached files" +msgstr "" + msgid "Attributes" msgstr "" diff --git a/geotrek/trekking/locale/fr/LC_MESSAGES/django.po b/geotrek/trekking/locale/fr/LC_MESSAGES/django.po index c89119a4cd..a80a7344ed 100644 --- a/geotrek/trekking/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/fr/LC_MESSAGES/django.po @@ -550,6 +550,9 @@ msgstr "Itinérance" msgid "Hike" msgstr "Randonnée" +msgid "Attached files" +msgstr "" + msgid "Attributes" msgstr "Informations" diff --git a/geotrek/trekking/locale/it/LC_MESSAGES/django.po b/geotrek/trekking/locale/it/LC_MESSAGES/django.po index eb1ef23ae9..a0807dcf3d 100644 --- a/geotrek/trekking/locale/it/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/it/LC_MESSAGES/django.po @@ -532,6 +532,9 @@ msgstr "itinerari a tappe" msgid "Hike" msgstr "Escursione" +msgid "Attached files" +msgstr "" + msgid "Attributes" msgstr "Attributi" diff --git a/geotrek/trekking/locale/nl/LC_MESSAGES/django.po b/geotrek/trekking/locale/nl/LC_MESSAGES/django.po index f54541a904..ee42f169bc 100644 --- a/geotrek/trekking/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/nl/LC_MESSAGES/django.po @@ -520,6 +520,9 @@ msgstr "" msgid "Hike" msgstr "Wandelen" +msgid "Attached files" +msgstr "" + msgid "Attributes" msgstr "" diff --git a/geotrek/trekking/models.py b/geotrek/trekking/models.py index 1285bdb483..4bd3cdc81e 100755 --- a/geotrek/trekking/models.py +++ b/geotrek/trekking/models.py @@ -205,6 +205,7 @@ class Trek(Topology, StructureRelated, PicturesMixin, PublishableMixin, GeotrekM reservation_id = models.CharField(verbose_name=_("Reservation ID"), max_length=1024, blank=True) attachments_accessibility = GenericRelation('common.AccessibilityAttachment') + view_points = GenericRelation('common.HDViewPoint', related_query_name='trek') capture_map_image_waitfor = '.poi_enum_loaded.services_loaded.info_desks_loaded.ref_points_loaded' @@ -707,6 +708,7 @@ class POI(StructureRelated, PicturesMixin, PublishableMixin, GeotrekMapEntityMix type = models.ForeignKey('POIType', related_name='pois', verbose_name=_("Type"), on_delete=models.CASCADE) eid = models.CharField(verbose_name=_("External id"), max_length=1024, blank=True, null=True) provider = models.CharField(verbose_name=_("Provider"), db_index=True, max_length=1024, blank=True) + view_points = GenericRelation('common.HDViewPoint', related_query_name='poi') geometry_types_allowed = ["POINT"] diff --git a/geotrek/trekking/templates/trekking/poi_detail.html b/geotrek/trekking/templates/trekking/poi_detail.html index 93f8aa1666..6a77617a82 100644 --- a/geotrek/trekking/templates/trekking/poi_detail.html +++ b/geotrek/trekking/templates/trekking/poi_detail.html @@ -1,10 +1,24 @@ {% extends "common/common_detail.html" %} -{% load static i18n mapentity_tags %} +{% load static i18n mapentity_tags geotrek_tags %} +{% block attachments_extra_tab_nav %} + {% with attachments_count=object.attachments.count|add:object.view_points.count %} + + {% endwith %%} +{% endblock %} {% block attachmentspanel %} {% include "trekking/ratio_info_fragment.html" %} {{ block.super }} + {% are_hdviews_enabled as are_hdviews_enabled %} + {% if are_hdviews_enabled %} +
+
+ {% include "common/hdviewpoint_detail_fragment.html" %} + {% endif %} {% endblock attachmentspanel %} diff --git a/geotrek/trekking/templates/trekking/trek_detail.html b/geotrek/trekking/templates/trekking/trek_detail.html index d71eb2a1b4..c88bfb74e5 100644 --- a/geotrek/trekking/templates/trekking/trek_detail.html +++ b/geotrek/trekking/templates/trekking/trek_detail.html @@ -23,6 +23,15 @@ {% endblock %} +{% block attachments_extra_tab_nav %} + {% with attachments_count=object.attachments.count|add:object.view_points.count %} + + {% endwith %%} +{% endblock %} + {% block after_attachments_extra_tab_content %} {% is_photos_accessibilities_enabled as enabled %} {% if enabled %} @@ -36,6 +45,12 @@ {% block attachmentspanel %} {% include "trekking/ratio_info_fragment.html" %} {{ block.super }} + {% are_hdviews_enabled as are_hdviews_enabled %} + {% if are_hdviews_enabled %} +
+
+ {% include "common/hdviewpoint_detail_fragment.html" %} + {% endif %} {% endblock attachmentspanel %} {% block detailspanel %} diff --git a/geotrek/trekking/views.py b/geotrek/trekking/views.py index 03d818d9da..ea9120ec58 100755 --- a/geotrek/trekking/views.py +++ b/geotrek/trekking/views.py @@ -20,7 +20,7 @@ from geotrek.common.mixins.api import APIViewSet from geotrek.common.mixins.forms import FormsetMixin from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin, MetaMixin -from geotrek.common.models import Attachment, RecordSource, TargetPortal, Label +from geotrek.common.models import Attachment, HDViewPoint, RecordSource, TargetPortal, Label from geotrek.common.permissions import PublicOrReadPermMixin from geotrek.common.views import DocumentPublic, DocumentBookletPublic, MarkupPublic from geotrek.common.viewsets import GeotrekMapentityViewSet @@ -100,7 +100,10 @@ def render_to_response(self, context): class TrekDetail(CompletenessMixin, MapEntityDetail): - queryset = Trek.objects.existing().select_related('topo_object') + queryset = Trek.objects.existing().select_related('topo_object').prefetch_related( + Prefetch('view_points', + queryset=HDViewPoint.objects.select_related('content_type', 'license')) + ) @property def icon_sizes(self): @@ -329,7 +332,10 @@ def get_queryset(self): class POIDetail(CompletenessMixin, MapEntityDetail): - queryset = POI.objects.existing() + queryset = POI.objects.existing().prefetch_related( + Prefetch('view_points', + queryset=HDViewPoint.objects.select_related('content_type', 'license')) + ) def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) diff --git a/geotrek/zoning/locale/de/LC_MESSAGES/django.po b/geotrek/zoning/locale/de/LC_MESSAGES/django.po index ccb0abd723..041b2c7aac 100644 --- a/geotrek/zoning/locale/de/LC_MESSAGES/django.po +++ b/geotrek/zoning/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/zoning/locale/en/LC_MESSAGES/django.po b/geotrek/zoning/locale/en/LC_MESSAGES/django.po index afaa482145..e09b890550 100644 --- a/geotrek/zoning/locale/en/LC_MESSAGES/django.po +++ b/geotrek/zoning/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/zoning/locale/es/LC_MESSAGES/django.po b/geotrek/zoning/locale/es/LC_MESSAGES/django.po index afaa482145..e09b890550 100644 --- a/geotrek/zoning/locale/es/LC_MESSAGES/django.po +++ b/geotrek/zoning/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/zoning/locale/fr/LC_MESSAGES/django.po b/geotrek/zoning/locale/fr/LC_MESSAGES/django.po index c6eac0c6d1..7a3b0d070c 100644 --- a/geotrek/zoning/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/zoning/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/zoning/locale/it/LC_MESSAGES/django.po b/geotrek/zoning/locale/it/LC_MESSAGES/django.po index 78d3a69ce4..c18039535d 100644 --- a/geotrek/zoning/locale/it/LC_MESSAGES/django.po +++ b/geotrek/zoning/locale/it/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/geotrek/zoning/locale/nl/LC_MESSAGES/django.po b/geotrek/zoning/locale/nl/LC_MESSAGES/django.po index afaa482145..e09b890550 100644 --- a/geotrek/zoning/locale/nl/LC_MESSAGES/django.po +++ b/geotrek/zoning/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-03 13:51+0000\n" +"POT-Creation-Date: 2022-12-16 09:52+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/requirements.txt b/requirements.txt index f0902edcce..d81b840847 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,8 @@ billiard==3.6.4.0 # via celery btrees==4.10.0 # via zodb +cachetools==5.2.0 + # via large-image cairocffi==0.9.0 # via # cairosvg @@ -43,6 +45,7 @@ cffi==1.15.0 # via # cairocffi # persistent + # pyvips # weasyprint charset-normalizer==3.0.1 # via requests @@ -86,6 +89,7 @@ django==3.2.16 # django-embed-video # django-filter # django-js-asset + # django-large-image # django-leaflet # django-modelcluster # django-modeltranslation @@ -120,6 +124,8 @@ django-filter==22.1 # via mapentity django-js-asset==2.0.0 # via django-mptt +django-large-image==0.8.1 + # via geotrek (setup.py) django-leaflet==0.19.post9 # via mapentity django-modelcluster==6.0 @@ -136,6 +142,7 @@ django-weasyprint==1.1.0.post2 # mapentity djangorestframework==3.13.1 # via + # django-large-image # djangorestframework-datatables # djangorestframework-gis # drf-extensions @@ -152,13 +159,17 @@ drf-dynamic-fields==0.4.0 drf-extensions==0.7.1 # via geotrek (setup.py) drf-yasg==1.20.0 - # via geotrek (setup.py) + # via + # django-large-image + # geotrek (setup.py) easy-thumbnails==2.7 # via # mapentity # paperclip env-file==2020.12.3 # via geotrek (setup.py) +filelock==3.8.0 + # via django-large-image fiona==1.8.21 # via # geotrek (setup.py) @@ -187,6 +198,12 @@ kombu==5.2.4 # via celery landez==2.5.0 # via geotrek (setup.py) +large-image==1.17.2 + # via + # django-large-image + # large-image-source-vips +large-image-source-vips==1.17.2 + # via geotrek (setup.py) lxml==4.9.1 # via mapentity mapentity==8.4.0 @@ -201,8 +218,16 @@ munch==2.5.0 # via fiona netifaces==0.11.0 # via mapentity +numpy==1.23.4 + # via + # large-image + # large-image-source-vips packaging==21.3 - # via drf-yasg + # via + # drf-yasg + # large-image-source-vips +palettable==3.3.0 + # via large-image paperclip==2.6.1 # via # geotrek (setup.py) @@ -221,10 +246,13 @@ pillow==9.3.0 # django-colorfield # easy-thumbnails # geotrek (setup.py) + # large-image # paperclip # weasyprint prompt-toolkit==3.0.30 # via click-repl +psutil==5.9.3 + # via large-image psycopg2==2.9.5 # via geotrek (setup.py) pycparser==2.21 @@ -247,6 +275,8 @@ pytz==2022.7.1 # django-modelcluster # djangorestframework # djangorestframework-datatables +pyvips==2.2.1 + # via large-image-source-vips rcssmin==1.1.0 # via django-compressor redis==4.4.2 diff --git a/setup.py b/setup.py index a019c86655..12c7bab160 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,8 @@ def run(self): 'drf-yasg', 'xlrd', 'landez', + 'large-image-source-vips', + 'django-large-image', 'celery', 'redis', 'django-celery-results',