Skip to content

Commit

Permalink
move report endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
submarcos committed Mar 13, 2024
1 parent 8b28051 commit 5e0c954
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 471 deletions.
73 changes: 69 additions & 4 deletions geotrek/api/tests/test_v2.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime
import json
from unittest import skipIf
from unittest import skipIf, mock

from dateutil.relativedelta import relativedelta
from django.conf import settings
Expand All @@ -15,7 +15,8 @@
from django.utils import timezone
from freezegun.api import freeze_time
from mapentity.tests.factories import SuperUserFactory
from rest_framework.test import APITestCase
from paperclip.models import random_suffix_regexp
from rest_framework.test import APITestCase, APIClient

from geotrek import __version__
from geotrek.api.v2.views.trekking import TrekViewSet
Expand All @@ -25,10 +26,11 @@
from geotrek.common.tests import factories as common_factory
from geotrek.common.utils.testdata import (get_dummy_uploaded_document,
get_dummy_uploaded_file,
get_dummy_uploaded_image)
get_dummy_uploaded_image, get_dummy_uploaded_image_svg)
from geotrek.core import models as path_models
from geotrek.core.tests import factories as core_factory
from geotrek.feedback.tests import factories as feedback_factory
from geotrek.feedback import models as feedback_models
from geotrek.feedback.tests import factories as feedback_factory, factories as feedback_factories
from geotrek.flatpages.tests import factories as flatpages_factory
from geotrek.infrastructure import models as infrastructure_models
from geotrek.infrastructure.tests import factories as infrastructure_factory
Expand Down Expand Up @@ -4702,3 +4704,66 @@ def test_cache_invalidates_along_x_forwarded_proto_header(self):
response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)))
data = response.json()
self.assertTrue(data['pictogram'].startswith('http://'))


class CreateReportsAPITest(TestCase):
@classmethod
def setUpTestData(cls):
cls.add_url = '/api/en/reports/report'
cls.data = {
'geom': '{"type": "Point", "coordinates": [3, 46.5]}',
'email': '[email protected]',
'activity': feedback_factories.ReportActivityFactory.create().pk,
'problem_magnitude': feedback_factories.ReportProblemMagnitudeFactory.create().pk
}

def post_report_data(self, data):
client = APIClient()
response = client.post(self.add_url, data=data,
allow_redirects=False)
self.assertEqual(response.status_code, 201, self.add_url)
return response

def test_reports_can_be_created_using_post(self):
self.post_report_data(self.data)
self.assertTrue(feedback_models.Report.objects.filter(email='[email protected]').exists())
report = feedback_models.Report.objects.get()
self.assertAlmostEqual(report.geom.x, 700000)
self.assertAlmostEqual(report.geom.y, 6600000)

def test_reports_can_be_created_without_geom(self):
self.data.pop('geom')
self.post_report_data(self.data)
self.assertTrue(feedback_models.Report.objects.filter(email='[email protected]').exists())

def test_reports_with_file(self):
self.data['image'] = get_dummy_uploaded_image()
self.post_report_data(self.data)
self.assertTrue(feedback_models.Report.objects.filter(email='[email protected]').exists())
report = feedback_models.Report.objects.get()
self.assertEqual(report.attachments.count(), 1)
regexp = f"dummy_img{random_suffix_regexp()}.jpeg"
self.assertRegex(report.attachments.first().attachment_file.name, regexp)
self.assertTrue(report.attachments.first().is_image)

@mock.patch('geotrek.api.v2.views.feedback.logger')
def test_reports_with_failed_image(self, mock_logger):
self.data['image'] = get_dummy_uploaded_image_svg()
self.data['comment'] = "We have a problem"
new_report_id = self.post_report_data(self.data).data.get('id')
self.assertTrue(feedback_models.Report.objects.filter(email='[email protected]').exists())
report = feedback_models.Report.objects.get(pk=new_report_id)
self.assertEqual(report.comment, "We have a problem")
mock_logger.error.assert_called_with(f"Failed to convert attachment dummy_img.svg for report {new_report_id}: cannot identify image file <InMemoryUploadedFile: dummy_img.svg (image/svg+xml)>")
self.assertEqual(report.attachments.count(), 0)

@mock.patch('geotrek.api.v2.views.feedback.logger')
def test_reports_with_bad_file_format(self, mock_logger):
self.data['image'] = get_dummy_uploaded_document()
self.data['comment'] = "We have a problem"
new_report_id = self.post_report_data(self.data).data.get('id')
self.assertTrue(feedback_models.Report.objects.filter(email='[email protected]').exists())
report = feedback_models.Report.objects.get(pk=new_report_id)
self.assertEqual(report.comment, "We have a problem")
mock_logger.error.assert_called_with(f"Invalid attachment dummy_file.odt for report {new_report_id} : {{\'attachment_file\': ['File mime type “text/plain” is not allowed for “odt”.']}}")
self.assertEqual(report.attachments.count(), 0)
34 changes: 32 additions & 2 deletions geotrek/api/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from bs4 import BeautifulSoup
from django.conf import settings
from django.contrib.gis.db.models.functions import Transform
from django.contrib.gis.geos import MultiLineString, Point
from django.contrib.gis.geos import MultiLineString, Point, GEOSGeometry
from django.db.models import F
from django.urls import reverse
from django.utils.html import escape
from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _
from drf_dynamic_fields import DynamicFieldsMixin
Expand All @@ -15,9 +16,11 @@
from easy_thumbnails.files import get_thumbnailer
from modeltranslation.utils import build_localized_fieldname
from PIL.Image import DecompressionBombError
from rest_framework import serializers
from rest_framework import serializers, serializers as rest_serializers
from rest_framework.relations import HyperlinkedIdentityField
from rest_framework_gis import serializers as geo_serializers
from rest_framework_gis.fields import GeometryField
from rest_framework_gis.serializers import GeoFeatureModelSerializer

from geotrek.api.v2.functions import Length3D
from geotrek.api.v2.mixins import PDFSerializerMixin, PublishedRelatedObjectsSerializerMixin
Expand Down Expand Up @@ -1522,3 +1525,30 @@ class BladeTypeSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
class Meta:
model = signage_models.BladeType
fields = ('id', 'label', 'structure')


class ReportAPISerializer(rest_serializers.ModelSerializer):
class Meta:
model = feedback_models.Report
id_field = 'id'
fields = ('id', 'email', 'activity', 'comment', 'category',
'status', 'problem_magnitude', 'related_trek',
'geom')
extra_kwargs = {
'geom': {'write_only': True},
}

def validate_geom(self, value):
return GEOSGeometry(value, srid=4326)

def validate_comment(self, value):
return escape(value)


class ReportAPIGeojsonSerializer(GeoFeatureModelSerializer, ReportAPISerializer):
# Annotated geom field with API_SRID
api_geom = GeometryField(read_only=True, precision=7)

class Meta(ReportAPISerializer.Meta):
geo_field = 'api_geom'
fields = ReportAPISerializer.Meta.fields + ('api_geom', )
83 changes: 83 additions & 0 deletions geotrek/api/v2/views/feedback.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import logging
import os

from PIL import Image
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.core.files import File
from django.core.mail import send_mail
from django.utils.translation import gettext as _
from rest_framework.mixins import CreateModelMixin
from rest_framework.parsers import FormParser, MultiPartParser
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import GenericViewSet

from geotrek.api.v2 import serializers as api_serializers, viewsets as api_viewsets
from geotrek.common.models import Attachment, FileType
from geotrek.feedback import models as feedback_models

logger = logging.getLogger(__name__)


class ReportStatusViewSet(api_viewsets.GeotrekViewSet):
serializer_class = api_serializers.ReportStatusSerializer
Expand All @@ -24,3 +43,67 @@ class ReportProblemMagnitudeViewSet(api_viewsets.GeotrekViewSet):
serializer_class = api_serializers.ReportProblemMagnitudeSerializer
queryset = feedback_models.ReportProblemMagnitude.objects \
.order_by('pk') # Required for reliable pagination


class ReportViewSet(GenericViewSet,
CreateModelMixin):
model = feedback_models.Report
parser_classes = [FormParser, MultiPartParser]
serializer_class = api_serializers.ReportAPISerializer
authentication_classes = []
permission_classes = [AllowAny]

def create(self, request, *args, **kwargs):
response = super().create(request)
creator, created = get_user_model().objects.get_or_create(
username="feedback", defaults={"is_active": False}
)
for file in request._request.FILES.values():
attachment = Attachment(
filetype=FileType.objects.get_or_create(type=settings.REPORT_FILETYPE)[
0
],
content_type=ContentType.objects.get_for_model(feedback_models.Report),
object_id=response.data.get("id"),
creator=creator,
attachment_file=file,
)
name, extension = os.path.splitext(file.name)
try:
attachment.full_clean() # Check that file extension and mimetypes are allowed
except ValidationError as e:
logger.error(f"Invalid attachment {name}{extension} for report {response.data.get('id')} : " + str(e))
else:
try:
# Reencode file to bitmap then back to jpeg lfor safety
if not os.path.exists(f"{settings.TMP_DIR}/report_file/"):
os.mkdir(f"{settings.TMP_DIR}/report_file/")
tmp_bmp_path = os.path.join(f"{settings.TMP_DIR}/report_file/", f"{name}.bmp")
tmp_jpeg_path = os.path.join(f"{settings.TMP_DIR}/report_file/", f"{name}.jpeg")
Image.open(file).save(tmp_bmp_path)
Image.open(tmp_bmp_path).save(tmp_jpeg_path)
with open(tmp_jpeg_path, 'rb') as converted_file:
attachment.attachment_file = File(converted_file, name=f"{name}.jpeg")
attachment.save()
os.remove(tmp_bmp_path)
os.remove(tmp_jpeg_path)
except Exception as e:
logger.error(f"Failed to convert attachment {name}{extension} for report {response.data.get('id')}: " + str(e))

if settings.SEND_REPORT_ACK and response.status_code == 201:
send_mail(
_("Geotrek : Signal a mistake"),
_(
"""Hello,
We acknowledge receipt of your feedback, thank you for your interest in Geotrek.
Best regards,
The Geotrek Team
http://www.geotrek.fr"""
),
settings.DEFAULT_FROM_EMAIL,
[request.data.get("email")],
)
return response
7 changes: 0 additions & 7 deletions geotrek/feedback/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,6 @@ def __init__(self, pending_requests_model=None):
self.AUTH = settings.SURICATE_MANAGEMENT_SETTINGS["AUTH"] if self.USE_AUTH else None


def test_suricate_connection():
print("API Standard :")
SuricateStandardRequestManager().test_suricate_connection()
print("API Gestion :")
SuricateGestionRequestManager().test_suricate_connection()


class SuricateMessenger:

def __init__(self, pending_requests_model=None):
Expand Down
10 changes: 8 additions & 2 deletions geotrek/feedback/management/commands/sync_suricate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from django.conf import settings
from django.core.management.base import BaseCommand

from geotrek.feedback.helpers import SuricateStandardRequestManager, SuricateGestionRequestManager
from geotrek.feedback.parsers import SuricateParser
from geotrek.feedback.helpers import test_suricate_connection

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -57,7 +57,7 @@ def handle(self, *args, **options):
report = options["report"]
no_notification = options["no_notif"]
if options['test']:
test_suricate_connection()
self.test_suricate_connection()
elif report is not None:
parser.get_alert(verbosity, report)
else:
Expand All @@ -69,3 +69,9 @@ def handle(self, *args, **options):
parser.get_alerts(verbosity=verbosity, should_notify=not (no_notification))
else:
logger.error("To use this command, please activate setting SURICATE_WORKFLOW_ENABLED.")

def test_suricate_connection(self):
self.stdout.write("API Standard :")
SuricateStandardRequestManager().test_suricate_connection()
self.stdout.write("API Gestion :")
SuricateGestionRequestManager().test_suricate_connection()
31 changes: 0 additions & 31 deletions geotrek/feedback/serializers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
from django.contrib.gis.geos import GEOSGeometry
from django.utils.html import escape
from drf_dynamic_fields import DynamicFieldsMixin
from mapentity.serializers import MapentityGeojsonModelSerializer
from rest_framework import serializers as rest_serializers
from rest_framework_gis.fields import GeometryField
from rest_framework_gis.serializers import GeoFeatureModelSerializer

from geotrek.feedback import models as feedback_models

Expand Down Expand Up @@ -32,33 +28,6 @@ class Meta(MapentityGeojsonModelSerializer.Meta):
fields = ["id", "name", "color"]


class ReportAPISerializer(rest_serializers.ModelSerializer):
class Meta:
model = feedback_models.Report
id_field = 'id'
fields = ('id', 'email', 'activity', 'comment', 'category',
'status', 'problem_magnitude', 'related_trek',
'geom')
extra_kwargs = {
'geom': {'write_only': True},
}

def validate_geom(self, value):
return GEOSGeometry(value, srid=4326)

def validate_comment(self, value):
return escape(value)


class ReportAPIGeojsonSerializer(GeoFeatureModelSerializer, ReportAPISerializer):
# Annotated geom field with API_SRID
api_geom = GeometryField(read_only=True, precision=7)

class Meta(ReportAPISerializer.Meta):
geo_field = 'api_geom'
fields = ReportAPISerializer.Meta.fields + ('api_geom', )


class ReportActivitySerializer(rest_serializers.ModelSerializer):
class Meta:
model = feedback_models.ReportActivity
Expand Down
Loading

0 comments on commit 5e0c954

Please sign in to comment.