From f099d69008ff0a0353006bdbb4f68ffeb9a435d8 Mon Sep 17 00:00:00 2001 From: Chatewgne Date: Mon, 5 Aug 2024 12:31:38 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20[FEAT]=20Add=20Annotation=20Categor?= =?UTF-8?q?y=20model=20for=20HD=20Views=20Annotations=20(refs=20#4032)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog.rst | 4 + geotrek/api/tests/test_v2.py | 103 +++++++++++++++++- geotrek/api/v2/serializers.py | 23 ++++ geotrek/api/v2/urls.py | 3 +- geotrek/api/v2/views/__init__.py | 2 +- geotrek/api/v2/views/common.py | 6 + geotrek/common/admin.py | 15 +++ geotrek/common/forms.py | 37 ++++++- .../common/locale/de/LC_MESSAGES/django.po | 8 +- .../common/locale/en/LC_MESSAGES/django.po | 8 +- .../common/locale/es/LC_MESSAGES/django.po | 8 +- .../common/locale/fr/LC_MESSAGES/django.po | 10 +- .../common/locale/it/LC_MESSAGES/django.po | 8 +- .../common/locale/nl/LC_MESSAGES/django.po | 8 +- .../0037_annotationcategory_and_more.py | 33 ++++++ geotrek/common/models.py | 13 +++ .../common/static/common/js/annotations.js | 41 ++++++- geotrek/common/static/common/style.css | 11 ++ .../common/hdviewpoint_annotation_form.html | 6 +- .../templates/common/sql/post_90_defaults.sql | 1 + geotrek/common/tests/factories.py | 8 ++ geotrek/common/tests/test_forms.py | 15 ++- geotrek/common/translation.py | 7 +- 23 files changed, 354 insertions(+), 24 deletions(-) create mode 100644 geotrek/common/migrations/0037_annotationcategory_and_more.py diff --git a/docs/changelog.rst b/docs/changelog.rst index eedfec7ae1..8436734f3c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,10 @@ CHANGELOG 2.108.0+dev (XXXX-XX-XX) ---------------------------- +**New features** + +- Add Annotation Categories to improve annotations on HD Views(refs #4032) + **Improvements** - Change infrastructure condition field to ManyToMany field (#3970) diff --git a/geotrek/api/tests/test_v2.py b/geotrek/api/tests/test_v2.py index 6f94d4dcfb..cffcbfc58d 100644 --- a/geotrek/api/tests/test_v2.py +++ b/geotrek/api/tests/test_v2.py @@ -57,6 +57,9 @@ from geotrek.zoning import models as zoning_models from geotrek.zoning.tests import factories as zoning_factory +ANNOTATION_CATEGORY_DETAIL_JSON_STRUCTURE = sorted([ + 'id', 'label', 'pictogram' +]) PAGINATED_JSON_STRUCTURE = sorted([ 'count', 'next', 'previous', 'results', @@ -479,8 +482,47 @@ def setUpTestData(cls): cls.site2.themes.add(cls.theme3) cls.label_3 = common_factory.LabelFactory() cls.site2.labels.add(cls.label_3) + cls.annotationcategory = common_factory.AnnotationCategoryFactory(label='A category') + annotations = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 259.26707830454814, + 148.1029483470403 + ] + }, + "properties": { + "name": "Point 1", + "annotationId": 1234, + "annotationType": "point", + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 540.4490176472651, + 161.10558138022952 + ] + }, + "properties": { + "name": "Point 2", + "annotationId": 2, + "annotationType": "point", + } + } + ] + } + annotations_categories = {'1234': str(cls.annotationcategory.pk)} cls.hdviewpoint_trek = common_factory.HDViewPointFactory( - content_object=cls.treks[0] + content_object=cls.treks[0], + annotations=annotations, + annotations_categories=annotations_categories ) cls.hdviewpoint_poi = common_factory.HDViewPointFactory( content_object=cls.poi @@ -829,6 +871,12 @@ def get_hdviewpoint_list(self, params=None): def get_hdviewpoint_detail(self, id_hdviewpoint, params=None): return self.client.get(reverse('apiv2:hdviewpoint-detail', args=(id_hdviewpoint,)), params) + def get_annotationcategory_list(self, params=None): + return self.client.get(reverse('apiv2:annotation-category-list'), params) + + def get_annotationcategory_detail(self, id_annotationcategory, params=None): + return self.client.get(reverse('apiv2:annotation-category-detail', args=(id_annotationcategory,)), params) + class NoPaginationTestCase(BaseApiTest): """ @@ -1502,6 +1550,18 @@ def test_hdviewpoint_list(self): common_models.HDViewPoint ) + def test_annotationcategory_detail(self): + self.check_structure_response( + self.get_annotationcategory_detail(self.annotationcategory.pk), + ANNOTATION_CATEGORY_DETAIL_JSON_STRUCTURE + ) + + def test_annotationcategory_list(self): + self.check_number_elems_response( + self.get_annotationcategory_list(), + common_models.AnnotationCategory + ) + def test_route_detail(self): self.check_structure_response( self.get_route_detail(self.route.pk), @@ -2466,7 +2526,46 @@ def test_hdviewpoint_detail_content(self): 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')) + annotations = json_response.get('annotations') + serialized_annotations = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 259.26707830454814, + 148.1029483470403 + ] + }, + "properties": { + "name": "Point 1", + "annotationId": 1234, + "annotationType": "point", + "category": self.annotationcategory.pk + } + }, + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 540.4490176472651, + 161.10558138022952 + ] + }, + "properties": { + "name": "Point 2", + "annotationId": 2, + "annotationType": "point", + "category": None + } + } + ] + } + self.assertEqual(str(self.annotationcategory), 'A category') + self.assertJSONEqual(json.dumps(serialized_annotations), json.dumps(annotations)) self.assertIsNone(json_response.get('site')) self.assertIsNone(json_response.get('poi')) self.assertEqual(json_response.get('trek').get('uuid'), str(self.treks[0].uuid)) diff --git a/geotrek/api/v2/serializers.py b/geotrek/api/v2/serializers.py index 48e49eb109..8e4b7df7a5 100644 --- a/geotrek/api/v2/serializers.py +++ b/geotrek/api/v2/serializers.py @@ -362,6 +362,7 @@ class Meta: class HDViewPointSerializer(TimeStampedSerializer): geometry = geo_serializers.GeometryField(read_only=True, source="geom_transformed", precision=7) picture_tiles_url = serializers.SerializerMethodField() + annotations = serializers.SerializerMethodField() thumbnail_url = serializers.SerializerMethodField() metadata_url = serializers.SerializerMethodField() trek = serializers.SerializerMethodField() @@ -399,6 +400,18 @@ def get_poi(self, obj): return {'uuid': related_obj.uuid, 'id': related_obj.id} return None + def get_annotations(self, obj): + annotations = obj.annotations + annotations_categories = obj.annotations_categories + for feature in annotations["features"]: + feat_id = feature["properties"]["annotationId"] + feat_type = feature["geometry"]["type"] + if feat_type == "Point" and str(feat_id) in annotations_categories.keys(): + feature["properties"]['category'] = int(annotations_categories[str(feat_id)]) + else: + feature["properties"]['category'] = None + return annotations + class Meta(TimeStampedSerializer.Meta): model = common_models.HDViewPoint fields = TimeStampedSerializer.Meta.fields + ( @@ -926,6 +939,16 @@ class Meta: model = trekking_models.Theme fields = ('id', 'label', 'pictogram') + class AnnotationCategorySerializer(DynamicFieldsMixin, serializers.ModelSerializer): + label = serializers.SerializerMethodField() + + def get_label(self, obj): + return get_translation_or_dict('label', self, obj) + + class Meta: + model = common_models.AnnotationCategory + fields = ('id', 'label', 'pictogram') + class AccessibilitySerializer(DynamicFieldsMixin, serializers.ModelSerializer): name = serializers.SerializerMethodField() diff --git a/geotrek/api/v2/urls.py b/geotrek/api/v2/urls.py index c3c5228164..16145b5e47 100644 --- a/geotrek/api/v2/urls.py +++ b/geotrek/api/v2/urls.py @@ -15,6 +15,8 @@ router.register('label', api_views.LabelViewSet, basename='label') router.register('organism', api_views.OrganismViewSet, basename='organism') router.register('file_type', api_views.FileTypeViewSet, basename='filetype') +router.register('hdviewpoint', api_views.HDViewPointViewSet, basename='hdviewpoint') +router.register('annotation_category', api_views.AnnotationCategoryViewSet, basename='annotation-category') if 'geotrek.core' in settings.INSTALLED_APPS: router.register('path', api_views.PathViewSet, basename='path') if 'geotrek.infrastructure' in settings.INSTALLED_APPS: @@ -82,7 +84,6 @@ router.register('signage_color', api_views.ColorViewSet, basename='signage-color') router.register('signage_direction', api_views.DirectionViewSet, basename='signage-direction') router.register('signage_condition', api_views.SignageConditionViewSet, basename='signage-condition') - 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 ecfd5ae948..1741b818c2 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, HDViewPointViewSet # noqa +from .common import TargetPortalViewSet, ThemeViewSet, SourceViewSet, ReservationSystemViewSet, LabelViewSet, OrganismViewSet, FileTypeViewSet, HDViewPointViewSet, AnnotationCategoryViewSet # 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 a5f225fb29..0d4b9973cb 100644 --- a/geotrek/api/v2/views/common.py +++ b/geotrek/api/v2/views/common.py @@ -122,3 +122,9 @@ def get_queryset(self): .prefetch_related('content_object') \ .annotate(geom_transformed=Transform(F('geom'), settings.API_SRID)) \ .order_by('title') # Required for reliable pagination + + +class AnnotationCategoryViewSet(api_viewsets.GeotrekViewSet): + filter_backends = api_viewsets.GeotrekViewSet.filter_backends + serializer_class = api_serializers.AnnotationCategorySerializer + queryset = common_models.AnnotationCategory.objects.all() diff --git a/geotrek/common/admin.py b/geotrek/common/admin.py index 1d18e06417..37a0f7192c 100644 --- a/geotrek/common/admin.py +++ b/geotrek/common/admin.py @@ -84,6 +84,20 @@ class ThemeAdmin(MergeActionMixin, TabbedTranslationAdmin): merge_field = 'label' +class AnnotationCategoryAdmin(MergeActionMixin, TabbedTranslationAdmin): + list_display = ('label', 'pictogram_img') + search_fields = ('label',) + merge_field = 'label' + + def has_delete_permission(self, request, obj=None): + if obj: + # Do not allow deleting a category that is used on an annotation + annotations_categories = common_models.HDViewPoint.objects.values_list('annotations_categories', flat=True) + used_annotations_categories = set(val for dic in annotations_categories for val in dic.values()) + return request.user.has_perm("common.delete_annotationcategory") and str(obj.pk) not in used_annotations_categories + return request.user.has_perm("common.delete_annotationcategory") + + class RecordSourceAdmin(admin.ModelAdmin): list_display = ('name', 'pictogram_img') search_fields = ('name', ) @@ -152,6 +166,7 @@ class AccessAdmin(MergeActionMixin, admin.ModelAdmin): admin.site.register(common_models.Attachment, AttachmentAdmin) admin.site.register(common_models.FileType, FileTypeAdmin) admin.site.register(common_models.Theme, ThemeAdmin) +admin.site.register(common_models.AnnotationCategory, AnnotationCategoryAdmin) admin.site.register(common_models.RecordSource, RecordSourceAdmin) admin.site.register(common_models.TargetPortal, TargetPortalAdmin) admin.site.register(common_models.ReservationSystem, ReservationSystemAdmin) diff --git a/geotrek/common/forms.py b/geotrek/common/forms.py index 1b013ffde3..2d206871a4 100644 --- a/geotrek/common/forms.py +++ b/geotrek/common/forms.py @@ -23,7 +23,7 @@ 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.models import AccessibilityAttachment, AnnotationCategory, HDViewPoint from geotrek.common.utils.translation import get_translated_fields from .mixins.models import NoDeleteMixin @@ -472,6 +472,11 @@ class Meta: class HDViewPointAnnotationForm(forms.ModelForm): annotations = forms.JSONField(label=False) + annotations_categories = forms.JSONField(label=False) + annotation_category = forms.ModelChoiceField( + required=False, + queryset=AnnotationCategory.objects.all() + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -490,6 +495,20 @@ def __init__(self, *args, **kwargs): 'style': 'display: none;' } ) + self.fields['annotations_categories'].required = False + self.fields['annotations_categories'].widget = forms.Textarea( + attrs={ + 'name': 'annotations_categories', + '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): @@ -502,6 +521,8 @@ def _init_layout(self): leftpanel = Div( 'annotations', + 'annotations_categories', + 'annotation_category', css_id="modelfields", ) formactions = FormActions( @@ -528,6 +549,18 @@ def _init_layout(self): formactions, ) + def clean_annotations_categories(self): + data = self.cleaned_data["annotations_categories"] + if data is None: + return {} + return data + + def clean_annotations(self): + data = self.cleaned_data["annotations"] + if data is None: + return {} + return data + class Meta: model = HDViewPoint - fields = ('annotations', ) + fields = ('annotations', 'annotations_categories') diff --git a/geotrek/common/locale/de/LC_MESSAGES/django.po b/geotrek/common/locale/de/LC_MESSAGES/django.po index 0c97272b0a..e3389b0b1e 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: 2024-05-28 12:55+0000\n" +"POT-Creation-Date: 2024-07-23 13:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -340,6 +340,12 @@ msgstr "" msgid "HD Views" msgstr "" +msgid "Annotation category" +msgstr "" + +msgid "Annotation categories" +msgstr "" + msgid "Access mean" msgstr "" diff --git a/geotrek/common/locale/en/LC_MESSAGES/django.po b/geotrek/common/locale/en/LC_MESSAGES/django.po index 0c97272b0a..e3389b0b1e 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: 2024-05-28 12:55+0000\n" +"POT-Creation-Date: 2024-07-23 13:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -340,6 +340,12 @@ msgstr "" msgid "HD Views" msgstr "" +msgid "Annotation category" +msgstr "" + +msgid "Annotation categories" +msgstr "" + msgid "Access mean" msgstr "" diff --git a/geotrek/common/locale/es/LC_MESSAGES/django.po b/geotrek/common/locale/es/LC_MESSAGES/django.po index d0e547039a..6756360c77 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: 2024-05-28 12:55+0000\n" +"POT-Creation-Date: 2024-07-23 13:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Olivia Duval \n" "Language-Team: LANGUAGE \n" @@ -340,6 +340,12 @@ msgstr "" msgid "HD Views" msgstr "" +msgid "Annotation category" +msgstr "" + +msgid "Annotation categories" +msgstr "" + msgid "Access mean" msgstr "" diff --git a/geotrek/common/locale/fr/LC_MESSAGES/django.po b/geotrek/common/locale/fr/LC_MESSAGES/django.po index 23d21ede68..cf9a25a571 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: 2024-05-28 12:55+0000\n" +"POT-Creation-Date: 2024-07-23 13:20+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" @@ -340,6 +340,12 @@ msgstr "" msgid "HD Views" msgstr "" +msgid "Annotation category" +msgstr "" + +msgid "Annotation categories" +msgstr "" + msgid "Access mean" msgstr "" diff --git a/geotrek/common/locale/nl/LC_MESSAGES/django.po b/geotrek/common/locale/nl/LC_MESSAGES/django.po index 0c97272b0a..e3389b0b1e 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: 2024-05-28 12:55+0000\n" +"POT-Creation-Date: 2024-07-23 13:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -340,6 +340,12 @@ msgstr "" msgid "HD Views" msgstr "" +msgid "Annotation category" +msgstr "" + +msgid "Annotation categories" +msgstr "" + msgid "Access mean" msgstr "" diff --git a/geotrek/common/migrations/0037_annotationcategory_and_more.py b/geotrek/common/migrations/0037_annotationcategory_and_more.py new file mode 100644 index 0000000000..d6aaa80801 --- /dev/null +++ b/geotrek/common/migrations/0037_annotationcategory_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2.13 on 2024-07-30 09:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0036_accessmean'), + ] + + operations = [ + migrations.CreateModel( + name='AnnotationCategory', + 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')), + ('pictogram', models.FileField(max_length=512, null=True, upload_to='upload', verbose_name='Pictogram')), + ('label', models.CharField(max_length=128, verbose_name='Name')), + ], + options={ + 'verbose_name': 'Annotation category', + 'verbose_name_plural': 'Annotation categories', + 'ordering': ['label'], + }, + ), + migrations.AddField( + model_name='hdviewpoint', + name='annotations_categories', + field=models.JSONField(blank=True, default=dict), + ), + ] diff --git a/geotrek/common/models.py b/geotrek/common/models.py index 38623e6d6b..84ce749556 100755 --- a/geotrek/common/models.py +++ b/geotrek/common/models.py @@ -328,6 +328,7 @@ class HDViewPoint(TimeStampedModelMixin, MapEntityMixin): content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) content_object = GenericForeignKey('content_type', 'object_id') annotations = models.JSONField(verbose_name=_("Annotations"), blank=True, default=dict) + annotations_categories = models.JSONField(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'), @@ -382,6 +383,18 @@ def get_annotate_url(self): return reverse('common:hdviewpoint_annotate', args=[self.pk]) +class AnnotationCategory(TimeStampedModelMixin, PictogramMixin): + label = models.CharField(verbose_name=_("Name"), max_length=128) + + class Meta: + verbose_name = _("Annotation category") + verbose_name_plural = _("Annotation categories") + ordering = ['label'] + + def __str__(self): + return self.label + + class AccessMean(TimeStampedModelMixin): label = models.CharField(max_length=128) diff --git a/geotrek/common/static/common/js/annotations.js b/geotrek/common/static/common/js/annotations.js index 72d3b6eade..a120dd2f47 100644 --- a/geotrek/common/static/common/js/annotations.js +++ b/geotrek/common/static/common/js/annotations.js @@ -238,13 +238,26 @@ function initAnnotationsWidget(map) { } var entry = $('#annotationlist .entry#sample').clone(); entry.attr({ id: '', 'annotation-id': id }); - entry.on('click', edit_label); + entry.find('.entry-name').on('click', edit_label); + if (annotation.type() === 'point') { + var category_selector = $('#id_annotation_category').clone(); + category_selector.attr('for-annotation', id); + category_selector.on("change", update_annotation_category_event) + entry.find('.entry-validate').before(category_selector); + } entry.find('.entry-name').text(annotation.name()); if (query.editing == id) { entry.find('.entry-adjust').hide(); entry.find('.entry-validate').show(); } $('#annotationlist').append(entry); + if (annotation.type() === 'point' & $("#div_id_annotations_categories textarea").val() !== "") { + var annotations_categories = JSON.parse($("#div_id_annotations_categories textarea").val()); + previous_category = annotations_categories[id] + if (typeof previous_category !== 'undefined') { + $('#id_annotation_category[for-annotation="' + id + '"]').val(parseInt(previous_category)) + } + } }); $('#annotationheader').css( 'display', $('#annotationlist .entry').length <= 1 ? 'none' : 'block'); @@ -258,10 +271,30 @@ function initAnnotationsWidget(map) { } } + function update_annotation_category(annotation_id, category_id) { + if ($("#div_id_annotations_categories textarea").val() != "") { + var annotations_categories = JSON.parse($("#div_id_annotations_categories textarea").val()) + } else { + var annotations_categories = {} + } + if (category_id == "") { + delete annotations_categories[annotation_id] + } else { + annotations_categories[annotation_id] = category_id + } + $("#div_id_annotations_categories textarea").val(JSON.stringify(annotations_categories)) + } + + function update_annotation_category_event(e) { + category_id = e.target.value + annotation_id = e.target.getAttribute('for-annotation') + update_annotation_category(annotation_id, category_id) + } + function edit_label(event) { - var entry = $(event.currentTarget); + var span = $(event.currentTarget); + entry = span.closest('.entry') // When clicking annotation name, display text input allowing to change it - var span = entry.find('.entry-name') if (span.find('input').length) { return } @@ -327,6 +360,7 @@ function initAnnotationsWidget(map) { handleAnnotationChange(evt); break; case 'remove': + update_annotation_category(id, "") layer.removeAnnotation(annotation); break; case 'remove-all': @@ -335,6 +369,7 @@ function initAnnotationsWidget(map) { layer.mode(null); layer.removeAllAnnotations(); layer.mode(mode); + $("#div_id_annotations_categories textarea").val("") fromButtonSelect = false; break; } diff --git a/geotrek/common/static/common/style.css b/geotrek/common/static/common/style.css index 93c3730170..2b416722e1 100644 --- a/geotrek/common/static/common/style.css +++ b/geotrek/common/static/common/style.css @@ -38,6 +38,14 @@ fieldset { flex-basis: 55%; } +#div_id_annotation_category { + display: none; +} + +#id_annotation_category { + width: 20rem; +} + .loader-wrapper { display: table; margin: 0 auto; @@ -169,7 +177,10 @@ fieldset { .entry { margin-right: 10px; + margin-bottom: 5px; + overflow: auto; } + .entry .entry-name { margin-left: 10px; margin-right: 10px; diff --git a/geotrek/common/templates/common/hdviewpoint_annotation_form.html b/geotrek/common/templates/common/hdviewpoint_annotation_form.html index 232a5aee1a..e4b3b33898 100644 --- a/geotrek/common/templates/common/hdviewpoint_annotation_form.html +++ b/geotrek/common/templates/common/hdviewpoint_annotation_form.html @@ -34,14 +34,10 @@ {% 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/sql/post_90_defaults.sql b/geotrek/common/templates/common/sql/post_90_defaults.sql index 1c7858d415..b176a0630a 100644 --- a/geotrek/common/templates/common/sql/post_90_defaults.sql +++ b/geotrek/common/templates/common/sql/post_90_defaults.sql @@ -100,6 +100,7 @@ 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 annotations_categories 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/tests/factories.py b/geotrek/common/tests/factories.py index 380d24ab37..f07b6d729e 100644 --- a/geotrek/common/tests/factories.py +++ b/geotrek/common/tests/factories.py @@ -174,3 +174,11 @@ class Meta: model = models.AccessMean label = factory.Sequence(lambda n: "Acces mean %s" % n) + + +class AnnotationCategoryFactory(factory.django.DjangoModelFactory): + class Meta: + model = models.AnnotationCategory + + label = factory.Sequence(lambda n: "Annotation Type %s" % n) + pictogram = factory.django.ImageField() diff --git a/geotrek/common/tests/test_forms.py b/geotrek/common/tests/test_forms.py index eed68fd074..fe5758c43c 100644 --- a/geotrek/common/tests/test_forms.py +++ b/geotrek/common/tests/test_forms.py @@ -1,7 +1,7 @@ import json from django.test import TestCase from geotrek.common.forms import HDViewPointAnnotationForm -from geotrek.common.tests.factories import HDViewPointFactory +from geotrek.common.tests.factories import AnnotationCategoryFactory, HDViewPointFactory from geotrek.trekking.tests.factories import TrekFactory @@ -9,12 +9,23 @@ class HDViewPointAnnotateFormTest(TestCase): @classmethod def setUpTestData(cls): cls.vp = HDViewPointFactory(content_object=TrekFactory()) + cls.annotationcategory = AnnotationCategoryFactory() 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 + "annotations": geojson, + "annotations_categories": f'{{"13":"{self.annotationcategory.pk}"}}' } form = HDViewPointAnnotationForm(instance=self.vp, data=data) form.save() self.assertEqual(self.vp.annotations, json.loads(geojson)) + self.assertEqual(self.vp.annotations_categories, {'13': str(self.annotationcategory.pk)}) + + def test_annotation_empty_values_form(self): + data = { + "annotations": "", + "annotations_categories": "" + } + form = HDViewPointAnnotationForm(instance=self.vp, data=data) + self.assertTrue(form.is_valid()) diff --git a/geotrek/common/translation.py b/geotrek/common/translation.py index 8d6415121b..eff4a25ea3 100644 --- a/geotrek/common/translation.py +++ b/geotrek/common/translation.py @@ -1,12 +1,16 @@ from modeltranslation.translator import translator, TranslationOptions -from geotrek.common.models import TargetPortal, Theme, Label, HDViewPoint +from geotrek.common.models import AnnotationCategory, TargetPortal, Theme, Label, HDViewPoint class ThemeTO(TranslationOptions): fields = ('label', ) +class AnnotationCategoryTO(TranslationOptions): + fields = ('label', ) + + class TargetPortalTO(TranslationOptions): fields = ('title', 'description') @@ -22,4 +26,5 @@ class HDViewPointTO(TranslationOptions): translator.register(Theme, ThemeTO) translator.register(TargetPortal, TargetPortalTO) translator.register(Label, LabelTO) +translator.register(AnnotationCategory, AnnotationCategoryTO) translator.register(HDViewPoint, HDViewPointTO)