Skip to content

Commit

Permalink
✨ [FEAT] Add Annotation Category model for HD Views Annotations (refs #…
Browse files Browse the repository at this point in the history
  • Loading branch information
Chatewgne committed Aug 5, 2024
1 parent 98f6eda commit f099d69
Show file tree
Hide file tree
Showing 23 changed files with 354 additions and 24 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
103 changes: 101 additions & 2 deletions geotrek/api/tests/test_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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))
Expand Down
23 changes: 23 additions & 0 deletions geotrek/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 + (
Expand Down Expand Up @@ -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()

Expand Down
3 changes: 2 additions & 1 deletion geotrek/api/v2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion geotrek/api/v2/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions geotrek/api/v2/views/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
15 changes: 15 additions & 0 deletions geotrek/common/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Check warning on line 97 in geotrek/common/admin.py

View check run for this annotation

Codecov / codecov/patch

geotrek/common/admin.py#L95-L97

Added lines #L95 - L97 were not covered by tests
return request.user.has_perm("common.delete_annotationcategory")


class RecordSourceAdmin(admin.ModelAdmin):
list_display = ('name', 'pictogram_img')
search_fields = ('name', )
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 35 additions & 2 deletions geotrek/common/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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):
Expand All @@ -502,6 +521,8 @@ def _init_layout(self):

leftpanel = Div(
'annotations',
'annotations_categories',
'annotation_category',
css_id="modelfields",
)
formactions = FormActions(
Expand All @@ -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')
8 changes: 7 additions & 1 deletion geotrek/common/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -340,6 +340,12 @@ msgstr ""
msgid "HD Views"
msgstr ""

msgid "Annotation category"
msgstr ""

msgid "Annotation categories"
msgstr ""

msgid "Access mean"
msgstr ""

Expand Down
8 changes: 7 additions & 1 deletion geotrek/common/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -340,6 +340,12 @@ msgstr ""
msgid "HD Views"
msgstr ""

msgid "Annotation category"
msgstr ""

msgid "Annotation categories"
msgstr ""

msgid "Access mean"
msgstr ""

Expand Down
8 changes: 7 additions & 1 deletion geotrek/common/locale/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -340,6 +340,12 @@ msgstr ""
msgid "HD Views"
msgstr ""

msgid "Annotation category"
msgstr ""

msgid "Annotation categories"
msgstr ""

msgid "Access mean"
msgstr ""

Expand Down
Loading

0 comments on commit f099d69

Please sign in to comment.