diff --git a/VERSION b/VERSION index 8496a2ebc0..ad22f48181 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.103.0+dev +2.103.2+dev diff --git a/debian/changelog b/debian/changelog index 58665790e8..77484c5234 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,20 @@ -geotrek-admin (2.103.0+dev) UNRELEASED; urgency=medium +geotrek-admin (2.103.2+dev) UNRELEASED; urgency=medium * - -- Célia Prat Thu, 14 Mar 2024 11:14:42 +0100 + -- Joaquim Nallar Fri, 22 Mar 2024 16:27:57 +0100 + +geotrek-admin (2.103.2) RELEASED; urgency=medium + + * New package release + + -- Joaquim Nallar Fri, 22 Mar 2024 16:03:23 +0100 + +geotrek-admin (2.103.1) RELEASED; urgency=medium + + * New package release + + -- Joaquim Nallar Fri, 15 Mar 2024 16:21:41 +0100 geotrek-admin (2.103.0) RELEASED; urgency=medium diff --git a/dev-requirements.in b/dev-requirements.in index 7b8fc7ddc4..ebef6b455e 100644 --- a/dev-requirements.in +++ b/dev-requirements.in @@ -1,6 +1,7 @@ -c requirements.txt pip-tools +lxml flake8 freezegun coverage diff --git a/dev-requirements.txt b/dev-requirements.txt index fdd9cdbb85..483550ed06 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -16,12 +16,12 @@ click==8.1.3 # pip-tools coverage==7.3.1 # via -r dev-requirements.in -django==3.2.24 +django==3.2.25 # via # -c requirements.txt # django-debug-toolbar # django-extensions -django-debug-toolbar==4.2.0 +django-debug-toolbar==4.3.0 # via -r dev-requirements.in django-extensions==3.2.3 # via -r dev-requirements.in @@ -37,6 +37,10 @@ importlib-metadata==6.8.0 # via # -c requirements.txt # build +lxml==4.9.3 + # via + # -c requirements.txt + # -r dev-requirements.in mccabe==0.7.0 # via flake8 packaging==21.3 diff --git a/docker/Dockerfile b/docker/Dockerfile index ec23a2b19f..a4238717e4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -48,6 +48,7 @@ RUN apt-get update -qq && apt-get install -y -qq \ libpangocairo-1.0-0 \ libgdk-pixbuf2.0-dev \ libffi-dev \ + fonts-dejavu-core \ libvips && \ apt-get install -y --no-install-recommends postgis && \ apt-get clean all && rm -rf /var/lib/apt/lists/* && rm -rf /var/cache/apt/* @@ -62,6 +63,7 @@ FROM base as build USER root RUN apt-get update -qq && apt-get install -y -qq \ + git \ python3.8-dev \ python3.8-venv \ build-essential \ diff --git a/docs/changelog.rst b/docs/changelog.rst index b18cebe054..54b179fa78 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,9 +2,46 @@ CHANGELOG ========= -2.103.0+dev (XXXX-XX-XX) +2.103.2+dev (XXXX-XX-XX) ------------------------ +**Breaking changes** + +- Geotrek-rando v2 support is deprecated, `sync_rando` command and Sync rando menu view are removed. + +**Hot fix** + +- Add git to Dockerfile build staging + + +2.103.2 (2024-03-22) +-------------------- + +**Bug fixes** + +- Fix bug deleted blades still displayed on detail view of signages (fix #4003) +- Fix interferences on `practice` mapping in Aggregator by changing calls order in GeotrekOutdoorParser (refs #3569) + +**Maintenance** + +- Bump mapentity from 8.7.1 to 8.7.2 + +**Development** + +- Add git to Dockerfile build staging + + +2.103.1 (2024-03-15) +-------------------- + +**Maintenance** + +- Bump mapentity from 8.7.0 to 8.7.1 + +**Hot fix** + +- Fix fonts in public PDF (docker image only) + 2.103.0 (2024-03-14) ------------------------ diff --git a/docs/install/advanced-configuration.rst b/docs/install/advanced-configuration.rst index 81b8deca30..b154b0140b 100644 --- a/docs/install/advanced-configuration.rst +++ b/docs/install/advanced-configuration.rst @@ -932,18 +932,6 @@ Attributes for "zones sensibles réglementaires" are: * ``url`` : card url. Optional. -Sync to Geotrek-rando -''''''''''''''''''''''' - -Just run: - -.. code-block :: bash - - sudo geotrek sync_rando - -If sensitivity module is enabled, sensitive areas will be automatically synchronized. - - Feedback reports settings ------------------------- @@ -981,9 +969,6 @@ To initialize Report forms (Geotrek-admin, Geotrek-rando-V2, Geotrek-rando-V3) l geotrek loaddata /opt/geotrek-admin/lib/python*/site-packages/geotrek/feedback/fixtures/basic.json -To make these lists available for your Geotrek-rando-V2, run ``sync_rando`` (see :ref:`synchronization `) - - **2 - Suricate Standard** This mode simply forwards all reports to Suricate, using the Standard API to post reports. @@ -1136,8 +1121,6 @@ Load alerts from Suricate (located in your bounding box) : geotrek sync_suricate -v 2 --no-notification -To make these lists available for your Geotrek-rando, run ``sync_rando`` (see :ref:`synchronization `) - - Then load extra required statuses for Reports and Interventions: .. code-block :: python @@ -2891,7 +2874,6 @@ Example of a customised template (``/opt/geotrek-admin/var/conf/extra_templates/ The default template may change in the future versions. You will be in charge of porting the modification to your copy. Test your modifications by exporting a trek or a content to PDF from Geotrek-admin application. -To get your modifications available for Rando application, launch the ``sync_rando`` command. PDF as booklet @@ -2978,34 +2960,6 @@ You might also need to deploy logo images in the following places : Settings for Geotrek-rando -------------------------- -Synchro Geotrek-rando -~~~~~~~~~~~~~~~~~~~~~ -With Geotrek-rando V2, there is a synchronization mechanism to expose Geotrek-admin contents in json files generated automatically. - -.. warning:: - This is no more used in Geotrek-rando V3. - - -.. envvar:: SYNC_RANDO_ROOT - - Path on your server where the data for Geotrek-rando website will be generated - - Example:: - - SYNC_RANDO_ROOT = os.path.join(VAR_DIR, 'data') - -.. note:: - - If you want to modify it, do not forget to import os at the top of the file. - - Check `import Python `_ , if you need any information - -.. envvar:: SYNC_RANDO_OPTIONS - - Options of the sync_rando command in Geotrek-admin interface. - - Example:: - - SYNC_RANDO_OPTIONS = {} - .. _distances: Distances diff --git a/docs/install/synchronization.rst b/docs/install/synchronization.rst index 87965fbc78..ff01ac0afb 100644 --- a/docs/install/synchronization.rst +++ b/docs/install/synchronization.rst @@ -8,29 +8,20 @@ Synchronization :local: :depth: 2 -Manual synchronization ----------------------- -To create data for Geotrek-rando (public web portal) and Geotrek-mobile (mobile phone app), -just run this command: - -:: - - sudo geotrek sync_rando /opt/geotrek-admin/var/data - -The parameter is the destination directory for synchronized data. -If you choose another directory, make sure the parent of this directory is writable by geotrek user. -Otherwise you will get a PermissionError message. - -If Geotrek-admin is not accessible on localhost:80, you have to use the ``--url`` option. -To make output less or more verbose, you can use the ``--verbose`` option. +Geotrek-mobile app v3 +--------------------- -Since version 2.4.0 of Geotrek-admin, you can also launch the command ``sync_rando`` from the web interface. -You can add synchronization options with advanced configuration setting ``SYNC_RANDO_OPTIONS = {}``. +The Geotrek-mobile app v3 has its own API and synchronization command called ``sync_mobile``. -For example, if you add this line in ``/opt/geotrek-admin/var/conf/custom.py`` you will skip generation of map tiles files during the synchronisation : -``SYNC_RANDO_OPTIONS = {'skip_tiles': True}`` +:: + geotrek sync_mobile [-h] [--languages LANGUAGES] [--portal PORTAL] + [--skip-tiles] [--url URL] [--indent INDENT] + [--version] [-v {0,1,2,3}] [--settings SETTINGS] + [--pythonpath PYTHONPATH] [--traceback] + [--no-color] [--force-color] + path Automatic synchronization ------------------------- @@ -39,81 +30,12 @@ You can set up automatic synchronization by creating a file ``/etc/cron.d/geotre :: - 0 3 * * * root /usr/sbin/geotrek sync_rando /opt/geotrek-admin/var/data + 0 3 * * * root /usr/sbin/geotrek sync_mobile /opt/geotrek-admin/var/data This example will automatically synchronize data a 3 am every day. Note: it is required to give the full path to the geotrek command since cron set the PATH only to `bin:/usr/bin`. -Synchronization options ------------------------ - -:: - - Options: - -v VERBOSITY, --verbosity=VERBOSITY - Verbosity level; 0=minimal output, 1=normal output, - 2=verbose output - -u URL, --url=URL Base URL of Geotrek-admin (eg. http://geotrek.myorganization.com) - -r URL, --rando-url=URL - Base URL of public Geotrek-rando website, used for static html versions of objects pages - generated for Facebook in meta folder of data API - -s SOURCE, --source=SOURCE - Filter by source(s) - -P PORTAL, --portal=PORTAL - Filter by portal(s) - -p, --skip-pdf Skip generation of PDF files - -t, --skip-tiles Skip generation of map tiles files for Geotrek-mobile app v2 - -d, --skip-dem Skip generation of Digital Elevation Model files for 3D view - -e, --skip-profile-png - Skip generation of PNG elevation profile - -w, --with-touristicevents - Include touristic events by trek in global.zip for Geotrek-mobile v2 - -c CONTENT_CATEGORIES, --with-touristiccontent-categories=CONTENT_CATEGORIES - Include touristic contents by trek in global.zip for Geotrek-mobile v2 - (filtered by category ID ex: --with-touristiccontent-categories="1,2,3") - -g, --with-signages Include published signages - -i, --with-infrastructures - Include published infrastructures - -Geotrek-mobile v3 uses its own synchronization command (see below). -If you are not using Geotrek-mobile v2 anymore, it is recommanded to use ``-t`` option to don't generate big offline tiles directories, -not used elsewhere than in Geotrek-mobile v2. Same for ``-w`` and ``-c`` option, only used for Geotrek-mobile v2. - - -Synchronization filtered by source and portal ---------------------------------------------- - -You can filter treks, touristic contents, touristic events and static pages by source(s). -For example, if you created 3 sources records named ``source A``, ``source B`` and ``source C`` -and you want to only export data from ``source A`` and ``source B`` to your web public portal, you can synchronize with: - -:: - - sudo geotrek sync_rando --source "source A,source B" dataAB - -Multiple sources are separated with comas (without space before or after coma). Do not forget to add double quotes after and before the parameter -if there are spaces in source names. -You can run several commands to export several sources combinations into several directories and use them to publish several distinct web portals. - -You can do exactly the same with ``Target_Portal`` field value. It will include objects associated to the selected portal + those without portal. - -:: - - sudo geotrek sync_rando --portal "portal A" dataA - - -Synchronization filtered by touristic content categories --------------------------------------------------------- - -In Geotrek-mobile v2, you can choose to also include touristic content per trek. You must specify ID categories : - -:: - - sudo geotrek sync_rando --with-touristiccontent-categories="1,3" - -Multiple categories are separated with comas (without space before or after coma). - Synchronization with a distant Geotrek-rando server --------------------------------------------------- @@ -126,20 +48,3 @@ If you have separated servers, you have to copy files, for example with ``rsync` :: rsync /path/of/generated/data other-server:/path/of/generated/data - - -Geotrek-mobile app v3 ---------------------- - -The Geotrek-mobile app v3 has its own API and synchronization command called ``sync_mobile``. - -It has similar parameters as ``sync_rando``: - -:: - - sudo geotrek sync_mobile [-h] [--languages LANGUAGES] [--portal PORTAL] - [--skip-tiles] [--url URL] [--indent INDENT] - [--version] [-v {0,1,2,3}] [--settings SETTINGS] - [--pythonpath PYTHONPATH] [--traceback] - [--no-color] [--force-color] - path diff --git a/docs/install/upgrade.rst b/docs/install/upgrade.rst index e6861c583e..50a75b7284 100644 --- a/docs/install/upgrade.rst +++ b/docs/install/upgrade.rst @@ -122,42 +122,6 @@ Restore files on the new server: tar xvzf data.tgz -Ubuntu bionic PostGIS 2.5 upgrade -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Geotrek-admin requires at least PostGIS 2.5. - -If you installed Geotrek-admin on bionic ubuntu with provided install method, you should update your database : -:: - - # Firstly, backup your database (see previous section) - # install postgresql APT repository - # (from https://wiki.postgresql.org/wiki/Apt) - - sudo apt install curl ca-certificates gnupg - curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null - sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' - sudo apt update - - # install postgis 2.5 on postgresql 10 - sudo apt install postgresql-10-postgis-2.5-scripts - sudo -u postgres psql -d geotrekdb -c "ALTER EXTENSION POSTGIS UPDATE"; # replace geotrekdb by your database name - - # You database is now using postgis 2.5 ! - - # Troubleshooting - # If you encounter error with last command to update postgis, just drop view v_projects and retry - # This view will be recreated after next Geotrek-admin upgrade or dpkg-reconfigure. - sudo -u postgres psql -d geotrekdb -c "DROP VIEW v_projects;"; - sudo -u postgres psql -d geotrekdb -c "ALTER EXTENSION POSTGIS UPDATE"; - - # Warning, by using postgresql official apt repo, next apt upgrade or apt full-upgrade will install postgresql-9.6 and postgis 3 along your database, because postgis meta-package has changed - # If your are not using postgresql-9.6, you can remove it (bionic postgresql default version is 10) - # sudo apt remove postgresql-9.6 - -If you use an external database, you should adapt this method along your system - - PostgreSQL ~~~~~~~~~~ @@ -211,7 +175,7 @@ Update PostgreSQL / PostGIS on Ubuntu Bionic :: - sudo rm /etc/apt/sources.list.d/pgdg.list + sudo rm -f /etc/apt/sources.list.d/pgdg.list sudo apt install curl ca-certificates sudo install -d /usr/share/postgresql-common/pgdg sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc @@ -219,11 +183,11 @@ Update PostgreSQL / PostGIS on Ubuntu Bionic sudo apt update -Then, make a database dump. +Then, make a database dump. You can see user / database / password in /opt/geotrek-admin/conf/env file. :: - sudo -u postgres pg_dump -Fc --no-acl --no-owner -d > /path/to/your/backup.dump + sudo -u postgres pg_dump -Fc --no-acl --no-owner -d > ./backup.dump Now, install newest version of PostgreSQL and PostGIS: @@ -260,12 +224,12 @@ Recreate user and database: .. warning:: - You should report configuration from /etc/postgresql/10/pg_hba.conf to /etc/postgresql/14/pg_hba.conf. + You should report configuration from /etc/postgresql/10/main/pg_hba.conf to /etc/postgresql/14/main/pg_hba.conf. Then restart your postgresql :: - sudo cp /etc/postgresql/10/pg_hba.conf /etc/postgresql/14/pg_hba.conf + sudo cp /etc/postgresql/10/main/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf sudo systemctl restart postgresql @@ -274,7 +238,13 @@ You can now restore your database dump. :: - pg_restore -p 5433 -U -d /path/to/your/backup.dump + pg_restore -h 127.0.0.1 -p 5433 -U -d ./backup.dump + +.. note:: + + Note you have to use `-h 127.0.0.1` to connect with the `geotrek` user (this user cannot connect with the default unix socket). Connecting with `geotrek` is important for restored entities to have the right owner. + Some errors can occurs, around extensions creation or spatial_ref_sys table content. + This is normal. We already create these extensions on previous steps. .. warning:: diff --git a/geotrek/altimetry/locale/de/LC_MESSAGES/django.po b/geotrek/altimetry/locale/de/LC_MESSAGES/django.po index 746529d8e5..5c133d999c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 746529d8e5..5c133d999c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 746529d8e5..5c133d999c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 5755f8efac..4e9542e160 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 746529d8e5..5c133d999c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 746529d8e5..5c133d999c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/serializers.py b/geotrek/altimetry/serializers.py deleted file mode 100644 index 225557c10b..0000000000 --- a/geotrek/altimetry/serializers.py +++ /dev/null @@ -1,30 +0,0 @@ -from rest_framework import serializers as rest_serializers -from django.urls import reverse -from django.utils.translation import get_language - -from geotrek.altimetry.models import AltimetryMixin - - -class AltimetrySerializerMixin(rest_serializers.ModelSerializer): - elevation_area_url = rest_serializers.SerializerMethodField() - elevation_svg_url = rest_serializers.SerializerMethodField() - altimetric_profile = rest_serializers.SerializerMethodField('get_altimetric_profile_url') - - class Meta: - fields = ('elevation_area_url', 'elevation_svg_url', 'altimetric_profile') + ( - tuple(AltimetryMixin.COLUMNS)) - - def get_elevation_area_url(self, obj): - appname = obj._meta.app_label - modelname = obj._meta.model_name - return reverse('%s:%s_elevation_area' % (appname, modelname), kwargs={'lang': get_language(), 'pk': obj.pk}) - - def get_elevation_svg_url(self, obj): - appname = obj._meta.app_label - modelname = obj._meta.model_name - return reverse('%s:%s_profile_svg' % (appname, modelname), kwargs={'lang': get_language(), 'pk': obj.pk}) - - def get_altimetric_profile_url(self, obj): - appname = obj._meta.app_label - modelname = obj._meta.model_name - return reverse('%s:%s_profile' % (appname, modelname), kwargs={'lang': get_language(), 'pk': obj.pk}) diff --git a/geotrek/altimetry/tests/test_views.py b/geotrek/altimetry/tests/test_views.py index 96f14d4221..cb55dd9cbe 100644 --- a/geotrek/altimetry/tests/test_views.py +++ b/geotrek/altimetry/tests/test_views.py @@ -11,7 +11,7 @@ class ProfileViewsTest(TestCase): @classmethod def setUpTestData(cls): - cls.user = UserFactory(password='booh') + cls.user = UserFactory() def test_profile_model_do_not_exist(self): self.client.force_login(user=self.user) diff --git a/geotrek/api/locale/de/LC_MESSAGES/django.po b/geotrek/api/locale/de/LC_MESSAGES/django.po index 585a2e78d5..27be6dddae 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -565,3 +565,18 @@ msgid "" "This field is deprecated and will be removed in next releases. Please start " "using '%(field)s'" msgstr "" + +msgid "Geotrek : Signal a mistake" +msgstr "" + +msgid "" +"Hello,\n" +"\n" +"We acknowledge receipt of your feedback, thank you for your interest in " +"Geotrek.\n" +"\n" +"Best regards,\n" +"\n" +"The Geotrek Team\n" +"http://www.geotrek.fr" +msgstr "" diff --git a/geotrek/api/locale/en/LC_MESSAGES/django.po b/geotrek/api/locale/en/LC_MESSAGES/django.po index 585a2e78d5..27be6dddae 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -565,3 +565,18 @@ msgid "" "This field is deprecated and will be removed in next releases. Please start " "using '%(field)s'" msgstr "" + +msgid "Geotrek : Signal a mistake" +msgstr "" + +msgid "" +"Hello,\n" +"\n" +"We acknowledge receipt of your feedback, thank you for your interest in " +"Geotrek.\n" +"\n" +"Best regards,\n" +"\n" +"The Geotrek Team\n" +"http://www.geotrek.fr" +msgstr "" diff --git a/geotrek/api/locale/es/LC_MESSAGES/django.po b/geotrek/api/locale/es/LC_MESSAGES/django.po index 585a2e78d5..27be6dddae 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -565,3 +565,18 @@ msgid "" "This field is deprecated and will be removed in next releases. Please start " "using '%(field)s'" msgstr "" + +msgid "Geotrek : Signal a mistake" +msgstr "" + +msgid "" +"Hello,\n" +"\n" +"We acknowledge receipt of your feedback, thank you for your interest in " +"Geotrek.\n" +"\n" +"Best regards,\n" +"\n" +"The Geotrek Team\n" +"http://www.geotrek.fr" +msgstr "" diff --git a/geotrek/api/locale/fr/LC_MESSAGES/django.po b/geotrek/api/locale/fr/LC_MESSAGES/django.po index 1e67516788..468234e8f1 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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" @@ -565,3 +565,18 @@ msgid "" "This field is deprecated and will be removed in next releases. Please start " "using '%(field)s'" msgstr "" + +msgid "Geotrek : Signal a mistake" +msgstr "" + +msgid "" +"Hello,\n" +"\n" +"We acknowledge receipt of your feedback, thank you for your interest in " +"Geotrek.\n" +"\n" +"Best regards,\n" +"\n" +"The Geotrek Team\n" +"http://www.geotrek.fr" +msgstr "" diff --git a/geotrek/api/locale/nl/LC_MESSAGES/django.po b/geotrek/api/locale/nl/LC_MESSAGES/django.po index 585a2e78d5..27be6dddae 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -565,3 +565,18 @@ msgid "" "This field is deprecated and will be removed in next releases. Please start " "using '%(field)s'" msgstr "" + +msgid "Geotrek : Signal a mistake" +msgstr "" + +msgid "" +"Hello,\n" +"\n" +"We acknowledge receipt of your feedback, thank you for your interest in " +"Geotrek.\n" +"\n" +"Best regards,\n" +"\n" +"The Geotrek Team\n" +"http://www.geotrek.fr" +msgstr "" diff --git a/geotrek/api/tests/test_mobile/test_sync_mobile.py b/geotrek/api/tests/test_mobile/test_sync_mobile.py index 5a06290d9b..507631f1e2 100644 --- a/geotrek/api/tests/test_mobile/test_sync_mobile.py +++ b/geotrek/api/tests/test_mobile/test_sync_mobile.py @@ -173,7 +173,7 @@ def test_language_not_in_db(self): def test_attachments_missing_from_disk(self, mocke): mocke.side_effect = Exception() trek_1 = TrekWithPublishedPOIsFactory.create(published_fr=True) - attachment = AttachmentFactory(content_object=trek_1, attachment_file=get_dummy_uploaded_image()) + attachment = AttachmentFactory(content_object=trek_1, attachment_file=get_dummy_uploaded_image(), is_image=True) os.remove(attachment.attachment_file.path) management.call_command('sync_mobile', os.path.join(settings.TMP_DIR, 'sync_mobile', 'tmp_sync'), url='http://localhost:8000', skip_tiles=True, languages='fr', verbosity=2, stdout=StringIO()) diff --git a/geotrek/api/tests/test_mobile/test_views.py b/geotrek/api/tests/test_mobile/test_views.py index d9adc13267..c911f39797 100644 --- a/geotrek/api/tests/test_mobile/test_views.py +++ b/geotrek/api/tests/test_mobile/test_views.py @@ -103,7 +103,7 @@ def test_launch_sync_mobile_fail(self, mocked_stdout, command): @patch('geotrek.api.management.commands.sync_mobile.Command.handle', return_value=None, side_effect=Exception('This is a test')) @patch('sys.stdout', new_callable=StringIO) - def test_launch_sync_rando_no_rando_root(self, mocked_stdout, command): + def test_launch_sync_mobile_no_root(self, mocked_stdout, command): task = launch_sync_mobile.apply() log = mocked_stdout.getvalue() self.assertNotIn("Done", log) diff --git a/geotrek/api/tests/test_v2.py b/geotrek/api/tests/test_v2.py index 3a13f8f018..2802e0003e 100644 --- a/geotrek/api/tests/test_v2.py +++ b/geotrek/api/tests/test_v2.py @@ -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 @@ -9,39 +9,45 @@ Point, Polygon) from django.contrib.gis.geos.collections import GeometryCollection from django.db import connection -from django.test import TestCase, RequestFactory -from django.test.utils import override_settings +from django.test import TestCase, RequestFactory, override_settings from django.urls import reverse from django.utils import timezone -from freezegun.api import freeze_time +from freezegun 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 from geotrek.authent import models as authent_models from geotrek.authent.tests import factories as authent_factory +from geotrek.authent.tests.factories import StructureFactory from geotrek.common import models as common_models -from geotrek.common.tests import factories as common_factory +from geotrek.common.tests import factories as common_factory, TranslationResetMixin 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 from geotrek.outdoor import models as outdoor_models from geotrek.outdoor.tests import factories as outdoor_factory from geotrek.sensitivity import models as sensitivity_models +from geotrek.sensitivity.models import SportPractice from geotrek.sensitivity.tests import factories as sensitivity_factory +from geotrek.sensitivity.tests.factories import SensitiveAreaFactory, MultiPolygonSensitiveAreaFactory, \ + RegulatorySensitiveAreaFactory from geotrek.signage import models as signage_models from geotrek.signage.tests import factories as signage_factory from geotrek.tourism import models as tourism_models from geotrek.tourism.tests import factories as tourism_factory from geotrek.trekking import models as trek_models from geotrek.trekking.tests import factories as trek_factory +from geotrek.trekking.tests.base import TrekkingManagerTest from geotrek.trekking.tests.factories import PracticeFactory from geotrek.zoning import models as zoning_models from geotrek.zoning.tests import factories as zoning_factory @@ -4702,3 +4708,287 @@ 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': 'yeah@you.com', + '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='yeah@you.com').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='yeah@you.com').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='yeah@you.com').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='yeah@you.com').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 ") + 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='yeah@you.com').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) + + +@freeze_time("2020-01-01") +class SensitivityAPIv2Test(TranslationResetMixin, TrekkingManagerTest): + def setUp(self): + super().setUp() + self.sensitivearea = SensitiveAreaFactory.create() + self.species = self.sensitivearea.species + self.pk = self.sensitivearea.pk + self.expected_properties = { + 'create_datetime': self.sensitivearea.date_insert.isoformat().replace('+00:00', 'Z'), + 'update_datetime': self.sensitivearea.date_update.isoformat().replace('+00:00', 'Z'), + 'description': "Blabla", + "elevation": None, + 'attachments': [], + 'contact': 'toto@tata.com', + 'kml_url': 'http://testserver/api/en/sensitiveareas/{pk}.kml'.format(pk=self.pk), + 'openair_url': 'http://testserver/api/en/sensitiveareas/{pk}/openair'.format(pk=self.pk), + 'info_url': self.species.url, + 'species_id': self.species.id, + "name": self.species.name, + "period": [False, False, False, False, False, True, True, False, False, False, False, False], + 'practices': [practice.pk for practice in self.species.practices.all()], + 'rules': [ + {'code': 'R1', + 'description': None, + 'id': self.sensitivearea.rules.all()[0].pk, + 'name': 'Rule1', + 'pictogram': 'http://testserver/media/picto_rule1.png', + 'url': 'http://url.com'}, + {'code': 'R2', + 'description': 'abcdefgh', + 'id': self.sensitivearea.rules.all()[1].pk, + 'name': 'Rule2', + 'pictogram': 'http://testserver/media/picto_rule2.png', + 'url': 'http://url.com'}], + 'provider': '', + 'structure': 'My structure', + 'published': True, + } + self.expected_geom = { + 'type': 'Polygon', + 'coordinates': [[ + [3.0, 46.5], + [3.0, 46.500027], + [3.0000391, 46.500027], + [3.0000391, 46.5], + [3.0, 46.5], + ]], + } + self.expected_result = dict(self.expected_properties) + self.expected_result['id'] = self.pk + self.expected_result['geometry'] = self.expected_geom + self.expected_result['url'] = 'http://testserver/api/v2/sensitivearea/{}/?format=json'.format(self.pk) + self.expected_geo_result = { + 'bbox': [3.0, 46.5, 3.0000391, 46.500027], + 'geometry': self.expected_geom, + 'type': 'Feature', + 'id': self.pk, + 'properties': dict(self.expected_properties), + } + self.expected_geo_result['properties']['url'] = 'http://testserver/api/v2/sensitivearea/{}/?format=geojson'.format(self.pk) + + @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) + def test_detail_sensitivearea(self): + url = reverse('apiv2:sensitivearea-detail', args=(self.pk,)) + params = {'format': 'json', 'period': 'ignore', 'language': 'en'} + response = self.client.get(url, params) + self.assertJSONEqual(response.content.decode(), self.expected_result) + + @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) + def test_detail_sensitivearea_regulatory(self): + self.sensitivearea = RegulatorySensitiveAreaFactory.create(species__period01=True) + url = reverse('apiv2:sensitivearea-detail', args=(self.sensitivearea.pk,)) + params = {'format': 'json', 'period': 'ignore', 'language': 'en'} + response = self.client.get(url, params) + self.assertIsNone(response.json()['species_id']) + + @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) + def test_list_sensitivearea(self): + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'period': 'ignore', 'language': 'en'} + response = self.client.get(url, params) + self.assertJSONEqual(response.content.decode(), { + 'count': 1, + 'previous': None, + 'next': None, + 'results': [self.expected_result], + }) + + @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) + def test_geo_detail_sensitivearea(self): + url = reverse('apiv2:sensitivearea-detail', args=(self.pk,)) + params = {'format': 'geojson', 'period': 'ignore', 'language': 'en'} + response = self.client.get(url, params) + self.assertJSONEqual(response.content.decode(), self.expected_geo_result) + + @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) + def test_geo_list_sensitivearea(self): + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'geojson', 'period': 'ignore', 'language': 'en'} + response = self.client.get(url, params) + self.assertJSONEqual(response.content.decode(), { + 'count': 1, + 'next': None, + 'previous': None, + 'type': 'FeatureCollection', + 'features': [self.expected_geo_result] + }) + + def test_no_duplicates_sensitivearea(self): + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'geojson', 'period': 'ignore', 'language': 'en'} + params['practices'] = ','.join([str(p.pk) for p in self.species.practices.all()]) + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 1, response.json()) + + def test_multipolygon(self): + sensitivearea = MultiPolygonSensitiveAreaFactory.create() + expected_geom = { + 'type': 'MultiPolygon', + 'coordinates': [ + [[ + [3.0, 46.5], + [3.0, 46.500027], + [3.0000391, 46.500027], + [3.0000391, 46.5], + [3.0, 46.5], + ]], + [[ + [3.0001304, 46.50009], + [3.0001304, 46.5001171], + [3.0001695, 46.5001171], + [3.0001695, 46.50009], + [3.0001304, 46.50009], + ]] + ], + } + url = reverse('apiv2:sensitivearea-detail', args=(sensitivearea.pk,)) + params = {'format': 'json', 'period': 'ignore', 'language': 'en'} + response = self.client.get(url, params) + self.assertEqual(response.json()['geometry'], expected_geom) + + @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) + def test_list_bubble_sensitivearea(self): + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'period': 'ignore', 'language': 'en', 'bubble': 'True'} + response = self.client.get(url, params) + self.expected_result[u'radius'] = None + self.assertJSONEqual(response.content.decode(), { + u'count': 1, + u'previous': None, + u'next': None, + u'results': [self.expected_result], + }) + + def test_list_bubble_sensitivearea_with_point(self): + sensitive_area_point = SensitiveAreaFactory.create(geom='SRID=2154;POINT (700040 6600040)', + species__period01=True, species__radius=5) + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'language': 'en', 'bubble': 'True', 'period': '1'} + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 1) + self.assertEqual(response.json()['results'][0]['radius'], 5) + self.assertEqual(response.json()['results'][0]['name'], sensitive_area_point.species.name) + + def test_list_sportpractice(self): + url = reverse('apiv2:sportpractice-list') + params = {'format': 'json', 'language': 'en'} + response = self.client.get(url, params) + sports_practice = SportPractice.objects.all() + result_sportpractice = [{'id': sp.id, 'name': sp.name} for sp in sports_practice] + self.assertJSONEqual(response.content.decode(), { + 'count': 2, + 'previous': None, + 'next': None, + 'results': result_sportpractice + }) + + def test_filters_structure(self): + other_structure = StructureFactory.create(name='other') + self.sensitivearea_other_structure = SensitiveAreaFactory.create(structure=other_structure) + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'period': 'ignore', 'language': 'en'} + params['structures'] = other_structure.pk + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 1) + self.assertEqual(response.json()['results'][0]['name'], self.sensitivearea_other_structure.species.name) + + def test_filters_no_period(self): + StructureFactory.create() + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'language': 'en'} + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 0) + + def test_filters_any_period(self): + SensitiveAreaFactory.create() + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'period': 'any', 'language': 'en'} + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 2) + + def test_filters_specific_period(self): + sensitive_area_jf = SensitiveAreaFactory.create(species__period01=True, species__period02=True) + SensitiveAreaFactory.create(species__period01=True) + SensitiveAreaFactory.create(species__period04=True) + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'period': '2,3', 'language': 'en'} + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 1) + self.assertEqual(response.json()['results'][0]['name'], sensitive_area_jf.species.name) + + def test_filters_no_period_get_month(self): + sensitive_area_month = SensitiveAreaFactory.create(**{'species__period01': True}) + SensitiveAreaFactory.create(**{'species__period02': True}) + url = reverse('apiv2:sensitivearea-list') + params = {'format': 'json', 'language': 'en'} + response = self.client.get(url, params) + self.assertEqual(response.json()['count'], 1) + self.assertEqual(response.json()['results'][0]['name'], sensitive_area_month.species.name) diff --git a/geotrek/api/v2/serializers.py b/geotrek/api/v2/serializers.py index 036e184823..29b032a6fd 100644 --- a/geotrek/api/v2/serializers.py +++ b/geotrek/api/v2/serializers.py @@ -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 @@ -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 @@ -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', ) diff --git a/geotrek/api/v2/urls.py b/geotrek/api/v2/urls.py index ca1980b948..e4981797ff 100644 --- a/geotrek/api/v2/urls.py +++ b/geotrek/api/v2/urls.py @@ -24,6 +24,7 @@ router.register('infrastructure_usage_difficulty_level', api_views.InfrastructureUsageDifficultyLevelViewSet, basename='infrastructure-usage-difficulty') router.register('infrastructure_maintenance_difficulty_level', api_views.InfrastructureMaintenanceDifficultyLevelViewSet, basename='infrastructure-maintenance-difficulty') if 'geotrek.feedback' in settings.INSTALLED_APPS: + router.register('feedback_report', api_views.ReportViewSet, basename='feedback-report') router.register('feedback_status', api_views.ReportStatusViewSet, basename='feedback-status') router.register('feedback_category', api_views.ReportCategoryViewSet, basename='feedback-category') router.register('feedback_activity', api_views.ReportActivityViewSet, basename='feedback-activity') diff --git a/geotrek/api/v2/views/__init__.py b/geotrek/api/v2/views/__init__.py index 7776c0badf..5c9d16efdf 100644 --- a/geotrek/api/v2/views/__init__.py +++ b/geotrek/api/v2/views/__init__.py @@ -10,7 +10,8 @@ if 'geotrek.core' in settings.INSTALLED_APPS: from .core import PathViewSet # noqa if 'geotrek.feedback' in settings.INSTALLED_APPS: - from .feedback import ReportStatusViewSet, ReportActivityViewSet, ReportCategoryViewSet, ReportProblemMagnitudeViewSet # noqa + from .feedback import (ReportStatusViewSet, ReportActivityViewSet, ReportCategoryViewSet, # noqa + ReportProblemMagnitudeViewSet, ReportViewSet) # noqa if 'geotrek.trekking' in settings.INSTALLED_APPS: from .trekking import (TrekViewSet, TourViewSet, POIViewSet, POITypeViewSet, AccessibilityViewSet, RouteViewSet, # noqa DifficultyViewSet, NetworkViewSet, PracticeViewSet, AccessibilityLevelViewSet, # noqa diff --git a/geotrek/api/v2/views/feedback.py b/geotrek/api/v2/views/feedback.py index fbda51dafe..f577d0317c 100644 --- a/geotrek/api/v2/views/feedback.py +++ b/geotrek/api/v2/views/feedback.py @@ -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 @@ -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 diff --git a/geotrek/authent/locale/de/LC_MESSAGES/django.po b/geotrek/authent/locale/de/LC_MESSAGES/django.po index 9a61621b24..e72e079779 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 9a61621b24..e72e079779 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 9a61621b24..e72e079779 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 d8b8fbca9e..82f095f5b7 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 9a61621b24..e72e079779 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 9a61621b24..e72e079779 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/views.py b/geotrek/authent/views.py deleted file mode 100755 index b7a1c74177..0000000000 --- a/geotrek/authent/views.py +++ /dev/null @@ -1,3 +0,0 @@ -""" - Generic views for authentication -""" diff --git a/geotrek/cirkwi/locale/de/LC_MESSAGES/django.po b/geotrek/cirkwi/locale/de/LC_MESSAGES/django.po index a7bcd1c90a..e74f4b67ac 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 a7bcd1c90a..e74f4b67ac 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 a7bcd1c90a..e74f4b67ac 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 c7ed4ab941..dfda6f299d 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 a7bcd1c90a..e74f4b67ac 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 a7bcd1c90a..e74f4b67ac 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/embed/backends.py b/geotrek/common/embed/backends.py index 940604e86e..935334ab79 100644 --- a/geotrek/common/embed/backends.py +++ b/geotrek/common/embed/backends.py @@ -3,18 +3,16 @@ class DailymotionBackend(VideoBackend): - """ - Backend for Dailymotion URLs. - """ + """ Backend for Dailymotion URLs. """ re_detect = re.compile( - r'^(http(s)?://)?(www\.)?dailymotion.com/.*', re.I + r'^(http(s)?://)?((www\.)?dailymotion.com/.*|dai\.ly/\w+)', re.I ) re_code = re.compile( - r'''dailymotion.com/ # match youtube's domains + r'''(dailymotion.com/|dai\.ly/) # match dailymotion's domains (embed/)? - video/ # match the embed url syntax - (?P\w{7}) # match and extract + (video/)? # match the embed url syntax + (?P\w+) # match and extract ''', re.I | re.X ) diff --git a/geotrek/common/forms.py b/geotrek/common/forms.py index 48310aaf8e..1b013ffde3 100644 --- a/geotrek/common/forms.py +++ b/geotrek/common/forms.py @@ -365,27 +365,6 @@ def __init__(self, *args, **kwargs): ) -class SyncRandoForm(forms.Form): - """ - Sync Rando View Form - """ - - @property - def helper(self): - helper = FormHelper() - helper.form_id = 'form-sync' - helper.form_action = reverse('common:sync_randos') - helper.form_class = 'search' - # submit button with boostrap attributes, disabled by default - helper.add_input(Button('sync-web', _("Launch Sync"), - css_class="btn-primary", - **{'data-toggle': "modal", - 'data-target': "#confirm-submit", - 'disabled': 'disabled'})) - - return helper - - class AttachmentAccessibilityForm(forms.ModelForm): next = forms.CharField(widget=forms.HiddenInput()) diff --git a/geotrek/common/helpers_sync.py b/geotrek/common/helpers_sync.py index 087f2cd4d8..4bce06df2b 100644 --- a/geotrek/common/helpers_sync.py +++ b/geotrek/common/helpers_sync.py @@ -5,8 +5,6 @@ from landez import TilesManager from landez.sources import DownloadError -from geotrek.common import models -from geotrek.common import views logger = logging.getLogger(__name__) @@ -54,13 +52,3 @@ def run(self): logger.warning("Failed to download tile %s" % name) else: self.zipfile.writestr(name, data) - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - models_picto = [models.Theme, models.RecordSource, models.Label] - self.global_sync.sync_pictograms(lang, models_picto, zipfile=self.global_sync.zipfile) - self.global_sync.sync_metas(lang, views.Meta) diff --git a/geotrek/common/locale/de/LC_MESSAGES/django.po b/geotrek/common/locale/de/LC_MESSAGES/django.po index 48b3886818..c2b1bc9f02 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-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -75,9 +75,6 @@ msgstr "" msgid "Data to import from local file" msgstr "" -msgid "Launch Sync" -msgstr "" - msgid "Overview of the tricky passage" msgstr "" @@ -102,21 +99,6 @@ msgstr "" msgid "Save changes" msgstr "" -msgid "Global tiles syncing ..." -msgstr "" - -msgid "Trek tiles syncing ..." -msgstr "" - -msgid "Tiles synced ..." -msgstr "" - -msgid "Language" -msgstr "" - -msgid "Sync ended" -msgstr "" - msgid "Geotrek Rando" msgstr "" @@ -485,9 +467,6 @@ msgstr "" msgid "Import from web." msgstr "" -msgid "Init sync ..." -msgstr "" - msgid "Photos to illustrate information related to the accessibility of treks." msgstr "" @@ -665,27 +644,6 @@ msgstr "" msgid "%(obj)s has changed state, it needs a review to be published. " msgstr "" -msgid "Sync verifications" -msgstr "" - -msgid "Sync in progress" -msgstr "" - -msgid "An error occured" -msgstr "" - -msgid "Public web site sync" -msgstr "" - -msgid "Confirmation" -msgstr "" - -msgid "Are you sure you want to sync ?" -msgstr "" - -msgid "Sync" -msgstr "" - #, python-format msgid "%s days" msgstr "" diff --git a/geotrek/common/locale/en/LC_MESSAGES/django.po b/geotrek/common/locale/en/LC_MESSAGES/django.po index 48b3886818..c2b1bc9f02 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-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -75,9 +75,6 @@ msgstr "" msgid "Data to import from local file" msgstr "" -msgid "Launch Sync" -msgstr "" - msgid "Overview of the tricky passage" msgstr "" @@ -102,21 +99,6 @@ msgstr "" msgid "Save changes" msgstr "" -msgid "Global tiles syncing ..." -msgstr "" - -msgid "Trek tiles syncing ..." -msgstr "" - -msgid "Tiles synced ..." -msgstr "" - -msgid "Language" -msgstr "" - -msgid "Sync ended" -msgstr "" - msgid "Geotrek Rando" msgstr "" @@ -485,9 +467,6 @@ msgstr "" msgid "Import from web." msgstr "" -msgid "Init sync ..." -msgstr "" - msgid "Photos to illustrate information related to the accessibility of treks." msgstr "" @@ -665,27 +644,6 @@ msgstr "" msgid "%(obj)s has changed state, it needs a review to be published. " msgstr "" -msgid "Sync verifications" -msgstr "" - -msgid "Sync in progress" -msgstr "" - -msgid "An error occured" -msgstr "" - -msgid "Public web site sync" -msgstr "" - -msgid "Confirmation" -msgstr "" - -msgid "Are you sure you want to sync ?" -msgstr "" - -msgid "Sync" -msgstr "" - #, python-format msgid "%s days" msgstr "" diff --git a/geotrek/common/locale/es/LC_MESSAGES/django.po b/geotrek/common/locale/es/LC_MESSAGES/django.po index d9c81ba10e..1cd175bba0 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-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Olivia Duval \n" "Language-Team: LANGUAGE \n" @@ -75,9 +75,6 @@ msgstr "" msgid "Data to import from local file" msgstr "" -msgid "Launch Sync" -msgstr "" - msgid "Overview of the tricky passage" msgstr "" @@ -102,21 +99,6 @@ msgstr "" msgid "Save changes" msgstr "" -msgid "Global tiles syncing ..." -msgstr "" - -msgid "Trek tiles syncing ..." -msgstr "" - -msgid "Tiles synced ..." -msgstr "" - -msgid "Language" -msgstr "" - -msgid "Sync ended" -msgstr "" - msgid "Geotrek Rando" msgstr "" @@ -485,9 +467,6 @@ msgstr "" msgid "Import from web." msgstr "" -msgid "Init sync ..." -msgstr "" - msgid "Photos to illustrate information related to the accessibility of treks." msgstr "" @@ -665,27 +644,6 @@ msgstr "tiene una geometría invalida;" msgid "%(obj)s has changed state, it needs a review to be published. " msgstr "" -msgid "Sync verifications" -msgstr "" - -msgid "Sync in progress" -msgstr "" - -msgid "An error occured" -msgstr "" - -msgid "Public web site sync" -msgstr "" - -msgid "Confirmation" -msgstr "" - -msgid "Are you sure you want to sync ?" -msgstr "" - -msgid "Sync" -msgstr "" - #, python-format msgid "%s days" msgstr "" diff --git a/geotrek/common/locale/fr/LC_MESSAGES/django.po b/geotrek/common/locale/fr/LC_MESSAGES/django.po index 6a342c5a43..adf1cf58a6 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-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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" @@ -75,9 +75,6 @@ msgstr "" msgid "Data to import from local file" msgstr "" -msgid "Launch Sync" -msgstr "" - msgid "Overview of the tricky passage" msgstr "" @@ -102,21 +99,6 @@ msgstr "" msgid "Save changes" msgstr "" -msgid "Global tiles syncing ..." -msgstr "" - -msgid "Trek tiles syncing ..." -msgstr "" - -msgid "Tiles synced ..." -msgstr "" - -msgid "Language" -msgstr "" - -msgid "Sync ended" -msgstr "" - msgid "Geotrek Rando" msgstr "" @@ -485,9 +467,6 @@ msgstr "" msgid "Import from web." msgstr "" -msgid "Init sync ..." -msgstr "" - msgid "Photos to illustrate information related to the accessibility of treks." msgstr "" @@ -665,27 +644,6 @@ msgstr "" msgid "%(obj)s has changed state, it needs a review to be published. " msgstr "" -msgid "Sync verifications" -msgstr "" - -msgid "Sync in progress" -msgstr "" - -msgid "An error occured" -msgstr "" - -msgid "Public web site sync" -msgstr "" - -msgid "Confirmation" -msgstr "" - -msgid "Are you sure you want to sync ?" -msgstr "" - -msgid "Sync" -msgstr "" - #, python-format msgid "%s days" msgstr "" diff --git a/geotrek/common/locale/nl/LC_MESSAGES/django.po b/geotrek/common/locale/nl/LC_MESSAGES/django.po index 48b3886818..c2b1bc9f02 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-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -75,9 +75,6 @@ msgstr "" msgid "Data to import from local file" msgstr "" -msgid "Launch Sync" -msgstr "" - msgid "Overview of the tricky passage" msgstr "" @@ -102,21 +99,6 @@ msgstr "" msgid "Save changes" msgstr "" -msgid "Global tiles syncing ..." -msgstr "" - -msgid "Trek tiles syncing ..." -msgstr "" - -msgid "Tiles synced ..." -msgstr "" - -msgid "Language" -msgstr "" - -msgid "Sync ended" -msgstr "" - msgid "Geotrek Rando" msgstr "" @@ -485,9 +467,6 @@ msgstr "" msgid "Import from web." msgstr "" -msgid "Init sync ..." -msgstr "" - msgid "Photos to illustrate information related to the accessibility of treks." msgstr "" @@ -665,27 +644,6 @@ msgstr "" msgid "%(obj)s has changed state, it needs a review to be published. " msgstr "" -msgid "Sync verifications" -msgstr "" - -msgid "Sync in progress" -msgstr "" - -msgid "An error occured" -msgstr "" - -msgid "Public web site sync" -msgstr "" - -msgid "Confirmation" -msgstr "" - -msgid "Are you sure you want to sync ?" -msgstr "" - -msgid "Sync" -msgstr "" - #, python-format msgid "%s days" msgstr "" diff --git a/geotrek/common/management/commands/sync_rando.py b/geotrek/common/management/commands/sync_rando.py deleted file mode 100644 index ce06b45348..0000000000 --- a/geotrek/common/management/commands/sync_rando.py +++ /dev/null @@ -1,542 +0,0 @@ -import argparse -import logging -import filecmp -import os -import stat -import shutil -import tempfile -from time import sleep -from zipfile import ZipFile - -from django.conf import settings -from django.contrib.auth.models import AnonymousUser -from django.core.management.base import BaseCommand, CommandError -from django.db.models import Q -from django.http import StreamingHttpResponse -from django.test.client import RequestFactory -from django.utils import translation -from django.utils.translation import gettext as _ - -from geotrek.common.models import FileType # NOQA -from geotrek.altimetry.views import ElevationProfile, ElevationArea, serve_elevation_chart -from geotrek.common import models as common_models - -from geotrek.tourism import models as tourism_models -from geotrek.trekking import models as trekking_models - -from geotrek.common import helpers_sync as common_sync -from geotrek.trekking import helpers_sync as trekking_sync - -if 'geotrek.diving' in settings.INSTALLED_APPS: - from geotrek.diving import helpers_sync as diving_sync -if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - from geotrek.sensitivity import helpers_sync as sensitivity_sync -if 'geotrek.signage' in settings.INSTALLED_APPS: - from geotrek.signage import helpers_sync as signage_sync -if 'geotrek.infrastructure' in settings.INSTALLED_APPS: - from geotrek.infrastructure import helpers_sync as infrastructure_sync -if 'geotrek.flatpages' in settings.INSTALLED_APPS: - from geotrek.flatpages import helpers_sync as flatpage_sync -if 'geotrek.feedback' in settings.INSTALLED_APPS: - from geotrek.feedback import helpers_sync as feedback_sync -if 'geotrek.tourism' in settings.INSTALLED_APPS: - from geotrek.tourism import helpers_sync as tourism_sync -# Register mapentity models -from geotrek.trekking import urls # NOQA -from geotrek.tourism import urls # NOQA - - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - def add_arguments(self, parser): - parser.add_argument('path') - parser.add_argument('--empty-tmp-folder', dest='empty_tmp_folder', action='store_true', default=False, - help='Empty tmp folder') - parser.add_argument('--url', '-u', dest='url', default='http://localhost', help='Base url') - parser.add_argument('--rando-url', '-r', dest='rando_url', default='http://localhost', - help='Base url of public rando site') - parser.add_argument('--source', '-s', dest='source', default=None, help='Filter by source(s)') - parser.add_argument('--portal', '-P', dest='portal', default=None, help='Filter by portal(s)') - parser.add_argument('--skip-pdf', '-p', action='store_true', dest='skip_pdf', default=False, - help='Skip generation of PDF files') - parser.add_argument('--skip-tiles', '-t', action='store_true', dest='skip_tiles', default=False, - help='Skip generation of zip tiles files') - parser.add_argument('--skip-dem', '-d', action='store_true', dest='skip_dem', default=False, - help='Skip generation of DEM files for 3D') - parser.add_argument('--skip-profile-png', '-e', action='store_true', dest='skip_profile_png', default=False, - help='Skip generation of PNG elevation profile'), - parser.add_argument('--languages', '-l', dest='languages', default='', help='Languages to sync') - parser.add_argument('--with-touristicevents', '-w', action='store_true', dest='with_events', default=False, - help='include touristic events') - parser.add_argument('--with-touristiccontent-categories', '-c', dest='content_categories', - default=None, help='include touristic contents ' - '(filtered by category ID ex: --with-touristiccontent-categories="1,2,3")'), - parser.add_argument('--with-signages', '-g', action='store_true', dest='with_signages', default=False, - help='include signages') - parser.add_argument('--with-infrastructures', '-i', action='store_true', dest='with_infrastructures', - default=False, help='include infrastructures') - parser.add_argument('--with-dives', action='store_true', dest='with_dives', - default=False, help='include dives') - parser.add_argument('--task', default=None, help=argparse.SUPPRESS) - - def mkdirs(self, name): - dirname = os.path.dirname(name) - if not os.path.exists(dirname): - os.makedirs(dirname) - - def get_params_portal(self, params): - if self.portal: - params['portal'] = self.portal - - def sync_global_tiles(self): - """ Creates a tiles file on the global extent. - """ - zipname = os.path.join('zip', 'tiles', 'global.zip') - - if self.verbosity == 2: - self.stdout.write("\x1b[36m**\x1b[0m \x1b[1m{name}\x1b[0m ...".format(name=zipname), ending="") - self.stdout._out.flush() - - global_extent = settings.LEAFLET_CONFIG['SPATIAL_EXTENT'] - - logger.info("Global extent is %s" % str(global_extent)) - global_file = os.path.join(self.tmp_root, zipname) - - logger.info("Build global tiles file...") - self.mkdirs(global_file) - - zipfile = ZipFile(global_file, 'w') - tiles = common_sync.ZipTilesBuilder(zipfile, **self.builder_args) - tiles.add_coverage(bbox=global_extent, - zoomlevels=settings.MOBILE_TILES_GLOBAL_ZOOMS) - tiles.run() - self.close_zip(zipfile, zipname) - - def sync_trek_tiles(self, trek): - """ Creates a tiles file for the specified Trek object. - """ - zipname = os.path.join('zip', 'tiles', '{pk}.zip'.format(pk=trek.pk)) - - if self.verbosity == 2: - self.stdout.write("{name} ...".format(name=zipname), ending="") - self.stdout._out.flush() - - trek_file = os.path.join(self.tmp_root, zipname) - - def _radius2bbox(lng, lat, radius): - return (lng - radius, lat - radius, - lng + radius, lat + radius) - - self.mkdirs(trek_file) - - zipfile = ZipFile(trek_file, 'w') - tiles = common_sync.ZipTilesBuilder(zipfile, **self.builder_args) - - geom = trek.geom - if geom.geom_type == 'MultiLineString': - geom = geom[0] # FIXME - geom.transform(4326) - - for (lng, lat) in geom.coords: - large = _radius2bbox(lng, lat, settings.MOBILE_TILES_RADIUS_LARGE) - small = _radius2bbox(lng, lat, settings.MOBILE_TILES_RADIUS_SMALL) - tiles.add_coverage(bbox=large, zoomlevels=settings.MOBILE_TILES_LOW_ZOOMS) - tiles.add_coverage(bbox=small, zoomlevels=settings.MOBILE_TILES_HIGH_ZOOMS) - - tiles.run() - self.close_zip(zipfile, zipname) - - def sync_view(self, lang, view, name, url='/', params={}, zipfile=None, fix2028=False, **kwargs): - if self.verbosity == 2: - self.stdout.write("{lang} {name} ...".format(lang=lang, name=name), ending="") - self.stdout._out.flush() - fullname = os.path.join(self.tmp_root, name) - self.mkdirs(fullname) - request = self.factory.get(url, params, HTTP_HOST=self.host, secure=self.secure) - request.LANGUAGE_CODE = lang - request.user = AnonymousUser() - try: - response = view(request, **kwargs) - if hasattr(response, 'render'): - response.render() - except Exception as e: - self.successfull = False - if self.verbosity == 2: - self.stdout.write("\x1b[3D\x1b[31mfailed ({})\x1b[0m".format(e)) - if settings.DEBUG: - raise - return - if response.status_code != 200: - self.successfull = False - if self.verbosity > 0: - self.stderr.write(self.style.ERROR("failed (HTTP {code})".format(code=response.status_code))) - return - f = open(fullname, 'wb') - if isinstance(response, StreamingHttpResponse): - content = b''.join(response.streaming_content) - else: - content = response.content - # Fix strange unicode characters 2028 and 2029 that make Geotrek-rando crash - if fix2028: - content = content.replace(b'\\u2028', b'\\n') - content = content.replace(b'\\u2029', b'\\n') - f.write(content) - f.close() - oldfilename = os.path.join(self.dst_root, name) - # If new file is identical to old one, don't recreate it. This will help backup - if os.path.isfile(oldfilename) and filecmp.cmp(fullname, oldfilename): - os.unlink(fullname) - os.link(oldfilename, fullname) - if self.verbosity == 2: - self.stdout.write("unchanged") - else: - if self.verbosity == 2: - self.stdout.write("generated") - # FixMe: Find why there are duplicate files. - if zipfile: - if name not in zipfile.namelist(): - zipfile.write(fullname, name) - - def sync_json(self, lang, viewset, name, zipfile=None, params={}, as_view_args=[], **kwargs): - view = viewset.as_view(*as_view_args) - name = os.path.join('api', lang, '{name}.json'.format(name=name)) - if self.source: - params['source'] = ','.join(self.source) - self.get_params_portal(params) - self.sync_view(lang, view, name, params=params, zipfile=zipfile, fix2028=True, **kwargs) - - def sync_geojson(self, lang, viewset, name, zipfile=None, params={}, **kwargs): - view = viewset.as_view({'get': 'list'}) - name = os.path.join('api', lang, name) - params = params.copy() - params.update({'format': 'geojson'}) - - if self.source: - params['source'] = ','.join(self.source) - - self.get_params_portal(params) - - self.sync_view(lang, view, name, params=params, zipfile=zipfile, fix2028=True, **kwargs) - - def sync_object_view(self, lang, obj, view, basename_fmt, zipfile=None, params={}, **kwargs): - translation.activate(lang) - modelname = obj._meta.model_name - name = os.path.join('api', lang, '{modelname}s'.format(modelname=modelname), str(obj.pk), - basename_fmt.format(obj=obj)) - self.sync_view(lang, view, name, params=params, zipfile=zipfile, pk=obj.pk, **kwargs) - translation.deactivate() - - def sync_profile_json(self, lang, obj, zipfile=None): - view = ElevationProfile.as_view(model=type(obj)) - self.sync_object_view(lang, obj, view, 'profile.json', zipfile=zipfile) - - def sync_profile_png(self, lang, obj, zipfile=None): - view = serve_elevation_chart - model_name = type(obj)._meta.model_name - self.sync_object_view(lang, obj, view, 'profile.png', zipfile=zipfile, model_name=model_name, from_command=True) - - def sync_dem(self, lang, obj): - if self.skip_dem: - return - view = ElevationArea.as_view(model=type(obj)) - self.sync_object_view(lang, obj, view, 'dem.json') - - def sync_metas(self, lang, metaview, obj=None): - params = {'rando_url': self.rando_url, 'lang': lang} - self.get_params_portal(params) - if obj: - name = os.path.join('meta', lang, obj.rando_url, 'index.html') - self.sync_view(lang, metaview.as_view(), name, pk=obj.pk, params=params) - else: - name = os.path.join('meta', lang, 'index.html') - self.sync_view(lang, metaview.as_view(), name, params=params) - - def sync_file(self, lang, name, src_root, url, zipfile=None): - url = url.strip('/') - src = os.path.join(src_root, name) - dst = os.path.join(self.tmp_root, url, name) - self.mkdirs(dst) - if not os.path.isfile(src): - self.successfull = False - if self.verbosity == 2: - self.stdout.write("\x1b[36m{lang}\x1b[0m \x1b[1m{url}/{name}\x1b[0m \x1b[31mfile does not exist\x1b[0m".format(lang=lang, url=url, name=name)) - return - if not os.path.isfile(dst): - os.link(src, dst) - if zipfile: - zipfile.write(dst, os.path.join(url, name)) - if self.verbosity == 2: - self.stdout.write("{lang} {url}/{name} copied".format(lang=lang, url=url, name=name)) - - def sync_static_file(self, lang, name): - self.sync_file(lang, name, settings.STATIC_ROOT, settings.STATIC_URL) - - def sync_media_file(self, lang, field, zipfile=None): - if field and field.name: - self.sync_file(lang, field.name, settings.MEDIA_ROOT, settings.MEDIA_URL, zipfile=zipfile) - - def sync_pictograms(self, lang, models, zipfile=None): - for model in models: - for obj in model.objects.all(): - self.sync_media_file(lang, obj.pictogram, zipfile=zipfile) - - def close_zip(self, zipfile, name): - oldzipfilename = os.path.join(self.dst_root, name) - zipfilename = os.path.join(self.tmp_root, name) - try: - oldzipfile = ZipFile(oldzipfilename, 'r') - except IOError: - uptodate = False - else: - old = set([(zi.filename, zi.CRC) for zi in oldzipfile.infolist()]) - new = set([(zi.filename, zi.CRC) for zi in zipfile.infolist()]) - uptodate = (old == new) - oldzipfile.close() - - zipfile.close() - if uptodate: - stat = os.stat(oldzipfilename) - os.utime(zipfilename, (stat.st_atime, stat.st_mtime)) - - if self.verbosity == 2: - if uptodate: - self.stdout.write("unchanged") - else: - self.stdout.write("zipped") - - def sync_tiles(self): - if not self.skip_tiles: - - if self.celery_task: - self.celery_task.update_state( - state='PROGRESS', - meta={ - 'name': self.celery_task.name, - 'current': 10, - 'total': 100, - 'infos': "{}".format(_("Global tiles syncing ...")) - } - ) - - self.sync_global_tiles() - - if self.celery_task: - self.celery_task.update_state( - state='PROGRESS', - meta={ - 'name': self.celery_task.name, - 'current': 20, - 'total': 100, - 'infos': "{}".format(_("Trek tiles syncing ...")) - } - ) - - treks = trekking_models.Trek.objects.existing().order_by('pk') - if self.source: - treks = treks.filter(source__name__in=self.source) - - if self.portal: - treks = treks.filter(Q(portal__name=self.portal) | Q(portal=None)) - - for trek in treks: - if trek.any_published or any([parent.any_published for parent in trek.parents]): - self.sync_trek_tiles(trek) - - if self.celery_task: - self.celery_task.update_state( - state='PROGRESS', - meta={ - 'name': self.celery_task.name, - 'current': 30, - 'total': 100, - 'infos': "{}".format(_("Tiles synced ...")) - } - ) - - def sync_pdf(self, lang, obj, view): - if self.skip_pdf: - return - try: - file_type = FileType.objects.get(type="Topoguide") - except FileType.DoesNotExist: - file_type = None - attachments = common_models.Attachment.objects.attachments_for_object_only_type(obj, file_type) - if attachments: - path = attachments[0].attachment_file.name - modelname = obj._meta.model_name - src = os.path.join(settings.MEDIA_ROOT, path) - dst = os.path.join(self.tmp_root, 'api', lang, '{modelname}s'.format(modelname=modelname), str(obj.pk), - obj.slug + '.pdf') - self.mkdirs(dst) - os.link(src, dst) - if self.verbosity == 2: - self.stdout.write("\x1b[36m{lang}\x1b[0m \x1b[1m{dst}\x1b[0m \x1b[32mcopied\x1b[0m".format(lang=lang, - dst=dst)) - elif settings.ONLY_EXTERNAL_PUBLIC_PDF: - return - else: - params = {} - if self.source: - params['source'] = self.source[0] - self.get_params_portal(params) - self.sync_object_view(lang, obj, view, '{obj.slug}.pdf', params=params, slug=obj.slug) - - def sync(self): - step_value = int(50 / len(settings.MODELTRANSLATION_LANGUAGES)) - current_value = 30 - self.sync_tiles() - subcommands = [trekking_sync.SyncRando(self), common_sync.SyncRando(self)] - if self.with_signages and 'geotrek.signage' in settings.INSTALLED_APPS: - subcommands.append(signage_sync.SyncRando(self)) - if self.with_infrastructures and 'geotrek.infrastructure' in settings.INSTALLED_APPS: - subcommands.append(infrastructure_sync.SyncRando(self)) - if 'geotrek.flatpages' in settings.INSTALLED_APPS: - subcommands.append(flatpage_sync.SyncRando(self)) - if 'geotrek.feedback' in settings.INSTALLED_APPS: - subcommands.append(feedback_sync.SyncRando(self)) - if self.with_dives and 'geotrek.diving' in settings.INSTALLED_APPS: - subcommands.append(diving_sync.SyncRando(self)) - if 'geotrek.tourism' in settings.INSTALLED_APPS: - subcommands.append(tourism_sync.SyncRando(self)) - if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - subcommands.append(sensitivity_sync.SyncRando(self)) - for subcommand in subcommands: - for lang in self.languages: - if self.celery_task: - self.celery_task.update_state( - state='PROGRESS', - meta={ - 'name': self.celery_task.name, - 'current': current_value + step_value, - 'total': 100, - 'infos': "{} : {} ...".format(_("Language"), lang) - } - ) - current_value = current_value + step_value - - zipname = os.path.join('zip', 'treks', lang, 'global.zip') - zipfullname = os.path.join(self.tmp_root, zipname) - self.mkdirs(zipfullname) - self.zipfile = ZipFile(zipfullname, 'w') - - translation.activate(lang) - subcommand.sync(lang) - translation.deactivate() - - if self.verbosity == 2: - self.stdout.write("{lang} {name} ...".format(lang=lang, name=zipname), ending="") - - self.close_zip(self.zipfile, zipname) - - self.sync_static_file('**', 'tourism/touristicevent.svg') - self.sync_pictograms('**', [tourism_models.InformationDeskType, tourism_models.TouristicContentCategory, - tourism_models.TouristicContentType, tourism_models.TouristicEventType]) - - def check_dst_root_is_empty(self): - if not os.path.exists(self.dst_root): - return - existing = set([os.path.basename(p) for p in os.listdir(self.dst_root)]) - remaining = existing - set(('api', 'media', 'meta', 'static', 'zip')) - if remaining: - raise CommandError("Destination directory contains extra data") - - def rename_root(self): - if os.path.exists(self.dst_root): - tmp_root2 = os.path.join(os.path.dirname(self.dst_root), 'deprecated_sync_rando') - os.rename(self.dst_root, tmp_root2) - shutil.rmtree(tmp_root2) - os.rename(self.tmp_root, self.dst_root) - os.chmod(self.dst_root, stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH) - os.mkdir(self.tmp_root) # Recreate otherwise python3.6 will complain it does not find the tmp dir at cleanup. - - def handle(self, *args, **options): - self.options = options - self.successfull = True - self.verbosity = options['verbosity'] - self.dst_root = options["path"].rstrip('/') - self.check_dst_root_is_empty() - url = options['url'] - if url.startswith('https://'): - self.secure = True - elif url.startswith('http://'): - self.secure = False - else: - raise CommandError('url parameter should start with http:// or https://') - self.referer = options['url'] - self.host = self.referer.split('://')[1] - self.rando_url = options['rando_url'] - if self.rando_url.endswith('/'): - self.rando_url = self.rando_url[:-1] - self.factory = RequestFactory() - self.skip_pdf = options['skip_pdf'] - self.skip_tiles = options['skip_tiles'] - self.skip_dem = options['skip_dem'] - self.skip_profile_png = options['skip_profile_png'] - self.source = options['source'] - if options['languages']: - for language in options['languages'].split(','): - if language not in settings.MODELTRANSLATION_LANGUAGES: - raise CommandError("Language {lang_n} doesn't exist. Select in these one : {langs}". - format(lang_n=language, langs=list(settings.MODELTRANSLATION_LANGUAGES))) - self.languages = options['languages'].split(',') - else: - self.languages = settings.MODELTRANSLATION_LANGUAGES - self.with_events = options.get('with_events', False) - self.categories = None - if options.get('content_categories', ""): - self.categories = options.get('content_categories', "").split(',') - self.with_signages = options.get('with_signages', False) - self.with_infrastructures = options.get('with_infrastructures', False) - self.with_dives = options.get('with_dives', False) - self.celery_task = options.get('task', None) - - if self.source is not None: - self.source = self.source.split(',') - - self.portal = options['portal'] - if isinstance(settings.MOBILE_TILES_URL, str): - tiles_url = settings.MOBILE_TILES_URL - else: - tiles_url = settings.MOBILE_TILES_URL[0] - self.builder_args = { - 'tiles_url': tiles_url, - 'tiles_headers': {"Referer": self.referer}, - 'ignore_errors': True, - 'tiles_dir': os.path.join(settings.VAR_DIR, 'tiles'), - } - sync_rando_tmp_dir = os.path.join(settings.TMP_DIR, 'sync_rando') - if options['empty_tmp_folder']: - for dir in os.listdir(sync_rando_tmp_dir): - shutil.rmtree(os.path.join(sync_rando_tmp_dir, dir)) - if not os.path.exists(settings.TMP_DIR): - os.mkdir(settings.TMP_DIR) - if not os.path.exists(sync_rando_tmp_dir): - os.mkdir(sync_rando_tmp_dir) - with tempfile.TemporaryDirectory(dir=sync_rando_tmp_dir) as tmp_dir: - self.tmp_root = tmp_dir - self.sync() - if self.celery_task: - self.celery_task.update_state( - state='PROGRESS', - meta={ - 'name': self.celery_task.name, - 'current': 100, - 'total': 100, - 'infos': "{}".format(_("Sync ended")) - } - ) - self.rename_root() - - done_message = 'Done' - if self.successfull: - done_message = self.style.SUCCESS(done_message) - - if self.verbosity >= 1: - self.stdout.write(done_message) - - if not self.successfull: - raise CommandError('Some errors raised during synchronization.') - - sleep(2) # end sleep to ensure sync page get result diff --git a/geotrek/common/mixins/api.py b/geotrek/common/mixins/api.py deleted file mode 100644 index 8e672edefc..0000000000 --- a/geotrek/common/mixins/api.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.contrib.gis.db.models.functions import Transform -from mapentity.settings import API_SRID -from rest_framework import viewsets, permissions - - -class APIViewSet(viewsets.ModelViewSet): - permission_classes = [permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_serializer_class(self): - renderer, media_type = self.perform_content_negotiation(self.request) - if getattr(renderer, 'format') == 'geojson': - return self.geojson_serializer_class - else: - return self.serializer_class - - def get_queryset(self): - return super().get_queryset().annotate(api_geom=Transform("geom", API_SRID)) diff --git a/geotrek/common/mixins/models.py b/geotrek/common/mixins/models.py index a39bccc72f..0eae9cf739 100644 --- a/geotrek/common/mixins/models.py +++ b/geotrek/common/mixins/models.py @@ -8,7 +8,7 @@ from django.conf import settings from django.core.mail import mail_managers from django.db import models -from django.db.models import Q, Max, Count +from django.db.models import Max, Count from django.template.defaultfilters import slugify from django.template.loader import render_to_string @@ -19,7 +19,6 @@ from easy_thumbnails.engine import NoSourceGenerator from easy_thumbnails.exceptions import InvalidImageFormatError from easy_thumbnails.files import get_thumbnailer -from embed_video.backends import detect_backend, VideoDoesntExistException from geotrek.common.mixins.managers import NoDeleteManager from geotrek.common.utils import classproperty, logger @@ -161,12 +160,11 @@ def resized_pictures(self): resized.append((picture, thdetail)) return resized - @property - def picture_print(self): + def get_thumbnail(self, alias): for picture in self.pictures: thumbnailer = get_thumbnailer(picture.attachment_file) try: - thumbnail = thumbnailer.get_thumbnail(aliases.get('print')) + thumbnail = thumbnailer.get_thumbnail(aliases.get(alias)) except (IOError, InvalidImageFormatError, DecompressionBombError) as e: logger.info(_("Image {} invalid or missing from disk: {}.").format(picture.attachment_file, e)) continue @@ -175,19 +173,13 @@ def picture_print(self): return thumbnail return None + @property + def picture_print(self): + return self.get_thumbnail('print') + @property def thumbnail(self): - for picture in self.pictures: - thumbnailer = get_thumbnailer(picture.attachment_file) - try: - thumbnail = thumbnailer.get_thumbnail(aliases.get('small-square')) - except (IOError, InvalidImageFormatError, DecompressionBombError) as e: - logger.info(_("Image {} invalid or missing from disk: {}.").format(picture.attachment_file, e)) - continue - thumbnail.author = picture.author - thumbnail.legend = picture.legend - return thumbnail - return None + return self.get_thumbnail('small-square') def resized_picture_mobile(self, root_pk): pictures = self.serializable_pictures_mobile(root_pk) @@ -206,57 +198,6 @@ def thumbnail_display(self): return _("None") return '' % os.path.join(settings.MEDIA_URL, thumbnail.name) - @property - def thumbnail_csv_display(self): - return '' if self.thumbnail is None else os.path.join(settings.MEDIA_URL, self.thumbnail.name) - - @property - def serializable_thumbnail(self): - th = self.thumbnail - if not th: - return None - return os.path.join(settings.MEDIA_URL, th.name) - - @property - def videos(self): - all_attachments = self.attachments.all().order_by('-starred') - return all_attachments.exclude(attachment_video='') - - @property - def serializable_videos(self): - serialized = [] - for att in self.videos: - video = detect_backend(att.attachment_video) - video.is_secure = True - try: - serialized.append({ - 'author': att.author, - 'title': att.title, - 'legend': att.legend, - 'backend': type(video).__name__.replace('Backend', ''), - 'url': video.get_url(), - 'code': video.code, - }) - except VideoDoesntExistException: - pass - return serialized - - @property - def files(self): - return self.attachments.exclude(Q(is_image=True) | Q(attachment_file='')).order_by('-starred') - - @property - def serializable_files(self): - serialized = [] - for att in self.files: - serialized.append({ - 'author': att.author, - 'title': att.title, - 'legend': att.legend, - 'url': att.attachment_file.url, - }) - return serialized - @property def sorted_attachments(self): return self.attachments.order_by('-starred', 'date_insert') diff --git a/geotrek/common/mixins/views.py b/geotrek/common/mixins/views.py index 2f3f063bd2..fa7eb03ec6 100644 --- a/geotrek/common/mixins/views.py +++ b/geotrek/common/mixins/views.py @@ -1,20 +1,16 @@ import os from io import BytesIO -from urllib.parse import urljoin from django.conf import settings from django.http import HttpResponseNotFound, HttpResponse from django.shortcuts import get_object_or_404 -from django.utils import translation from django.utils.functional import classproperty -from django.utils.translation import gettext as _ from django.views import static from mapentity import views as mapentity_views from mapentity.helpers import suffix_for -from modeltranslation.utils import build_localized_fieldname from pdfimpose import PageList -from geotrek.common.models import TargetPortal, FileType, Attachment +from geotrek.common.models import FileType, Attachment from geotrek.common.utils import logger from geotrek.common.utils.portals import smart_get_template_by_portal @@ -164,31 +160,6 @@ def transform_pdf_booklet_callback(response): response.content = result.getvalue() -class MetaMixin: - def get_context_data(self, **kwargs): - lang = self.request.GET.get('lang') - portal = self.request.GET.get('portal') - context = super().get_context_data(**kwargs) - context['FACEBOOK_APP_ID'] = settings.FACEBOOK_APP_ID - context['FACEBOOK_IMAGE'] = urljoin(self.request.GET['rando_url'], settings.FACEBOOK_IMAGE) - context['FACEBOOK_IMAGE_WIDTH'] = settings.FACEBOOK_IMAGE_WIDTH - context['FACEBOOK_IMAGE_HEIGHT'] = settings.FACEBOOK_IMAGE_HEIGHT - translation.activate(lang) - context['META_TITLE'] = _('Geotrek Rando') - translation.deactivate() - if portal: - try: - target_portal = TargetPortal.objects.get(name=portal) - context['FACEBOOK_APP_ID'] = target_portal.facebook_id - context['FACEBOOK_IMAGE'] = urljoin(self.request.GET['rando_url'], target_portal.facebook_image_url) - context['FACEBOOK_IMAGE_WIDTH'] = target_portal.facebook_image_width - context['FACEBOOK_IMAGE_HEIGHT'] = target_portal.facebook_image_height - context['META_TITLE'] = getattr(target_portal, build_localized_fieldname('title', lang)) - except TargetPortal.DoesNotExist: - pass - return context - - class DocumentPortalMixin: def get_context_data(self, **kwargs): diff --git a/geotrek/common/serializers.py b/geotrek/common/serializers.py index 342f9a6585..dbd69f46f0 100644 --- a/geotrek/common/serializers.py +++ b/geotrek/common/serializers.py @@ -1,16 +1,9 @@ -from django.conf import settings 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_gis.fields import (GeometryField, - GeometrySerializerMethodField) -from rest_framework_gis.serializers import GeoFeatureModelSerializer +from rest_framework_gis.fields import GeometrySerializerMethodField -from .models import (Attachment, FileType, HDViewPoint, Label, RecordSource, - TargetPortal, Theme) +from .models import HDViewPoint class TranslatedModelSerializer(rest_serializers.ModelSerializer): @@ -45,58 +38,6 @@ class Meta: fields = ('published', 'published_status', 'publication_date') -class PublishableSerializerMixin(BasePublishableSerializerMixin): - printable = rest_serializers.SerializerMethodField('get_printable_url') - filelist_url = rest_serializers.SerializerMethodField() - - def get_printable_url(self, obj): - if settings.ONLY_EXTERNAL_PUBLIC_PDF: - file_type = get_object_or_404(FileType, type="Topoguide") - if not Attachment.objects.attachments_for_object_only_type(obj, file_type).exists(): - return None - appname = obj._meta.app_label - modelname = obj._meta.model_name - return reverse('%s:%s_printable' % (appname, modelname), - kwargs={'lang': get_language(), 'pk': obj.pk, 'slug': obj.slug}) - - def get_filelist_url(self, obj): - appname = obj._meta.app_label - modelname = obj._meta.model_name - return reverse('get_attachments', kwargs={'app_label': appname, - 'model_name': modelname, - 'pk': obj.pk}) - - class Meta: - fields = ('name', 'slug', 'map_image_url', 'filelist_url', 'printable') + \ - BasePublishableSerializerMixin.Meta.fields - - -class ThemeSerializer(PictogramSerializerMixin, TranslatedModelSerializer): - class Meta: - model = Theme - fields = ('id', 'pictogram', 'label') - - -class RecordSourceSerializer(PictogramSerializerMixin, rest_serializers.ModelSerializer): - class Meta: - model = RecordSource - fields = ('name', 'website', 'pictogram') - - -class TargetPortalSerializer(rest_serializers.ModelSerializer): - class Meta: - model = TargetPortal - fields = ('name', 'website') - - -class LabelSerializer(PictogramSerializerMixin, TranslatedModelSerializer): - filter_rando = rest_serializers.ReadOnlyField(source='filter') - - class Meta: - model = Label - fields = ('id', 'pictogram', 'name', 'advice', 'filter_rando') - - class HDViewPointSerializer(TranslatedModelSerializer): class Meta: model = HDViewPoint @@ -120,12 +61,3 @@ 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/tasks.py b/geotrek/common/tasks.py index f54ffc8758..7e616a4960 100644 --- a/geotrek/common/tasks.py +++ b/geotrek/common/tasks.py @@ -1,11 +1,9 @@ import importlib -import os from os.path import join import sys from celery import Task, shared_task, current_task from django.contrib.auth.models import User -from django.core.management import call_command from django.conf import settings from django.utils.translation import gettext_lazy as _ @@ -125,53 +123,3 @@ def progress_cb(progress, line, eid): 'report': parser.report(output_format='html').replace('$celery_id', current_task.request.id), 'name': current_task.name } - - -@shared_task(base=GeotrekImportTask, name='geotrek.trekking.sync-rando') -def launch_sync_rando(*args, **kwargs): - """ - celery shared task - sync rando command - """ - - if not os.path.exists(settings.TMP_DIR): - os.mkdir(settings.TMP_DIR) - sync_rando_tmp_dir = os.path.join(settings.TMP_DIR, 'sync_rando') - if not os.path.exists(sync_rando_tmp_dir): - os.mkdir(sync_rando_tmp_dir) - if not os.path.exists(settings.SYNC_RANDO_ROOT): - os.mkdir(settings.SYNC_RANDO_ROOT) - - print('Sync rando started') - - try: - current_task.update_state( - state='PROGRESS', - meta={ - 'name': current_task.name, - 'current': 5, - 'total': 100, - 'infos': "{}".format(_("Init sync ...")) - } - ) - - sync_rando_options = { - 'url': kwargs.get('url'), - } - sync_rando_options.update(settings.SYNC_RANDO_OPTIONS) - - call_command( - 'sync_rando', - settings.SYNC_RANDO_ROOT, - verbosity=2, - task=current_task, - **sync_rando_options - ) - - except Exception: - raise - - print('Sync rando ended') - - return { - 'name': current_task.name, - } diff --git a/geotrek/common/templates/common/meta.html b/geotrek/common/templates/common/meta.html deleted file mode 100644 index 6b7752b3e3..0000000000 --- a/geotrek/common/templates/common/meta.html +++ /dev/null @@ -1,32 +0,0 @@ -{% load i18n %}{% get_current_language as LANGUAGE_CODE %} - - - - - {{ META_TITLE }} - - - - - - - - - - - -

{{ META_TITLE }}

-{% for trek in treks %} - {{ trek.name }} -{% endfor %} -{% for content in contents %} - {{ content.name }} -{% endfor %} -{% for event in events %} - {{ event.name }} -{% endfor %} -{% for dive in dives %} - {{ dive.name }} -{% endfor %} - - diff --git a/geotrek/common/templates/common/sync_rando.html b/geotrek/common/templates/common/sync_rando.html deleted file mode 100644 index f417e3b726..0000000000 --- a/geotrek/common/templates/common/sync_rando.html +++ /dev/null @@ -1,137 +0,0 @@ -{% extends "mapentity/base_site.html" %} - -{% load static crispy_forms_tags %} -{% load i18n %} - -{% block extrahead %} - - -{% endblock extrahead %} - -{% block toolbar %} -{% endblock toolbar %} - -{% block mainpanel %} - -
-

{% trans "Public web site sync" %}

-
-
- -
- {% block mainform %} - {% crispy form form.helper %} - {% endblock mainform %} -
- - - - -{% endblock mainpanel %} diff --git a/geotrek/common/tests/__init__.py b/geotrek/common/tests/__init__.py index 4019fff7d6..4983639e18 100644 --- a/geotrek/common/tests/__init__.py +++ b/geotrek/common/tests/__init__.py @@ -12,20 +12,16 @@ from django.conf import settings from django.urls import reverse from django.urls.exceptions import NoReverseMatch - -# Workaround https://code.djangoproject.com/ticket/22865 -from freezegun import freeze_time - -from geotrek.common.models import Attachment, AccessibilityAttachment, FileType # NOQA -from geotrek.common.tests.factories import AttachmentFactory, AttachmentAccessibilityFactory -from geotrek.common.utils.testdata import get_dummy_uploaded_image - from mapentity.tests.factories import SuperUserFactory, UserFactory from mapentity.registry import app_settings from mapentity.tests import MapEntityTest, MapEntityLiveTest from geotrek.authent.tests.factories import StructureFactory from geotrek.authent.tests.base import AuthentFixturesTest +from geotrek.common.models import Attachment, AccessibilityAttachment, FileType # NOQA +from geotrek.common.utils.testdata import get_dummy_uploaded_image + +from .factories import AttachmentFactory, AttachmentAccessibilityFactory class TranslationResetMixin: @@ -47,7 +43,8 @@ def test_document_public_booklet_export(self, mock_requests): if self.model is None: return # Abstract test should not run try: - reverse(f'{self.model._meta.app_label}:{self.model._meta.model_name}_booklet_printable') + reverse(f'{self.model._meta.app_label}:{self.model._meta.model_name}_booklet_printable', + kwargs={'lang': 'en', 'pk': 0, 'slug': 'test'}) except NoReverseMatch: return # No public booklet export mock_requests.get.return_value.status_code = 200 @@ -130,7 +127,8 @@ def test_document_public_export(self, mock_requests): if self.model is None: return # Abstract test should not run try: - reverse(f'{self.model._meta.app_label}:{self.model._meta.model_name}_printable') + reverse(f'{self.model._meta.app_label}:{self.model._meta.model_name}_printable', + kwargs={'lang': 'en', 'pk': 0, 'slug': 'test'}) except NoReverseMatch: return # No public booklet export mock_requests.get.return_value.status_code = 200 @@ -284,101 +282,6 @@ def test_custom_columns_mixin_on_export(self): self.expected_column_formatlist_extra) -class GeotrekAPITestCase: - api_prefix = '/api/en/' - - def get_expected_json_attrs(self): - return {} - - @freeze_time("2020-03-17") - def test_api_list_for_model(self): - if self.get_expected_json_attrs is None: - return - if self.model is None: - return # Abstract test should not run - - self.obj = self.modelfactory.create() - list_url = '{api_prefix}{modelname}s.json'.format(api_prefix=self.api_prefix, - modelname=self.model._meta.model_name) - response = self.client.get(list_url) - self.assertEqual(response.status_code, 200, f"{list_url} not found") - content_json = response.json() - if hasattr(self, 'length'): - length = content_json[0].pop('length') - self.assertAlmostEqual(length, self.length) - self.assertEqual(content_json, [{'id': self.obj.pk, **self.get_expected_json_attrs()}]) - - @freeze_time("2020-03-17") - def test_api_geojson_list_for_model(self): - if self.get_expected_json_attrs is None: - return - if self.model is None: - return # Abstract test should not run - - self.obj = self.modelfactory.create() - list_url = '{api_prefix}{modelname}s.geojson'.format(api_prefix=self.api_prefix, - modelname=self.model._meta.model_name) - response = self.client.get(list_url) - self.assertEqual(response.status_code, 200, f"{list_url} not found") - content_json = response.json() - if hasattr(self, 'length'): - length = content_json['features'][0]['properties'].pop('length') - self.assertAlmostEqual(length, self.length) - self.assertEqual(content_json, { - 'type': 'FeatureCollection', - 'features': [{ - 'id': self.obj.pk, - 'type': 'Feature', - 'geometry': self.expected_json_geom, - 'properties': self.get_expected_json_attrs(), - }], - }) - - @freeze_time("2020-03-17") - def test_api_detail_for_model(self): - if self.get_expected_json_attrs is None: - return - if self.model is None: - return # Abstract test should not run - - self.obj = self.modelfactory.create() - detail_url = '{api_prefix}{modelname}s/{id}'.format(api_prefix=self.api_prefix, - modelname=self.model._meta.model_name, - id=self.obj.pk) - response = self.client.get(detail_url) - self.assertEqual(response.status_code, 200, f"{detail_url} not found") - - content_json = response.json() - if hasattr(self, 'length'): - length = content_json.pop('length') - self.assertAlmostEqual(length, self.length) - self.assertEqual(content_json, {'id': self.obj.pk, **self.get_expected_json_attrs()}) - - @freeze_time("2020-03-17") - def test_api_geojson_detail_for_model(self): - if self.get_expected_json_attrs is None: - return - if self.model is None: - return # Abstract test should not run - - self.obj = self.modelfactory.create() - detail_url = '{api_prefix}{modelname}s/{id}.geojson'.format(api_prefix=self.api_prefix, - modelname=self.model._meta.model_name, - id=self.obj.pk) - response = self.client.get(detail_url) - self.assertEqual(response.status_code, 200, f"{detail_url} not found") - content_json = response.json() - if hasattr(self, 'length'): - length = content_json['properties'].pop('length') - self.assertAlmostEqual(length, self.length) - self.assertEqual(content_json, { - 'id': self.obj.pk, - 'type': 'Feature', - 'geometry': self.expected_json_geom, - 'properties': self.get_expected_json_attrs(), - }) - - class CommonLiveTest(MapEntityLiveTest): @mock.patch('mapentity.helpers.requests') def test_map_image_other_language(self, mock_requests): diff --git a/geotrek/common/tests/deleteme b/geotrek/common/tests/deleteme deleted file mode 100644 index 8f2eb5fe6f..0000000000 --- a/geotrek/common/tests/deleteme +++ /dev/null @@ -1 +0,0 @@ -{'type': 'COMMERCE_ET_SERVICE', 'id': 882110, 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'gestion': {'dateModification': '2022-02-16T14:07:04.406+0000', 'membreProprietaire': {'nom': 'Office de Tourisme des Combrailles'}}, 'informations': {'moyensCommunication': [{'identifiant': 37594558, 'type': {'elementReferenceType': 'MoyenCommunicationType', 'id': 201, 'libelleFr': 'Téléphone', 'ordre': 1}, 'coordonnees': {'fr': '06 11 11 28 07'}}, {'identifiant': 36192830, 'type': {'elementReferenceType': 'MoyenCommunicationType', 'id': 204, 'libelleFr': 'Mél', 'ordre': 2}, 'coordonnees': {'fr': 'marchand.de.cailloux@cegetel.net'}}, {'identifiant': 37594559, 'type': {'elementReferenceType': 'MoyenCommunicationType', 'id': 207, 'libelleFr': 'Page facebook', 'ordre': 7}, 'coordonnees': {'fr': 'https://www.facebook.com/Marchand-De-Cailloux-Karine-186634461823244/'}}]}, 'presentation': {'descriptifCourt': {'libelleFr': "Artisanat d'art. Karine vous accueille et réalise vos commandes en lave émaillée. Elle saura aussi vous conseiller dans le choix de vos pierres et cristaux. Émaux sur lave, bijoux lave, argent et fantaisie, minéraux, lithothérapie et produits dérivés."}, 'descriptifDetaille': {'libelleFr': "Des créations originales sur mesure totalement inaltérables: cadrans solaires, tableaux, numéros et plaques de maison, dessous de plat, trophées, funéraire, glaçons, bijoux de lave... Et tant d'autres choses encore. Venez découvrir ce magasin insolite qui concilie tout l'art des Émaux sur lave et les connaissances de la minéralogie / lithothérapie. Poterie, artisanat du monde, savons, encens.\r\nAccueil de groupe sur rendez vous préférable."}}, 'localisation': {'adresse': {'adresse1': '4 rue Fernand Andant', 'codePostal': '63230', 'commune': {'id': 26108, 'code': '63285', 'nom': 'Pontgibaud', 'pays': {'elementReferenceType': 'Pays', 'id': 532, 'libelleFr': 'France', 'ordre': 78}, 'codePostal': '63230'}}, 'geolocalisation': {'geoJson': {'coordinates': [2.8509, 45.83201]}}}, 'prestations': {'services': [{'elementReferenceType': 'PrestationService', 'id': 687, 'libelleFr': 'Animaux acceptés', 'ordre': 328}]}, 'ouverture': {'periodeEnClair': {'libelleFr': "Du 01/01 au 31/12/2022.\nFermé le lundi.\nJuillet et août, ouvert tous les jours: 9h30 à 12h30 et 14h30 à 19h, fermé dimanche après-midi.\r\nDécembre, juin et septembre: 10h à 12h et 15h à 19h\r\nLes reste de l'année: 10h à 12h et 15h à 18h30."}}, 'descriptionTarif': {'modesPaiement': [{'elementReferenceType': 'ModePaiement', 'id': 1271, 'libelleFr': 'Chèque', 'ordre': 10}]}, 'illustrations': [{'identifiant': 12281589, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de cailloux'}, 'legende': {'libelleFr': 'Vitrine'}, 'copyright': {'libelleFr': 'Martin'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/79/134/12420687.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/79/134/12420687-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/79/134/12420687-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/79/134/12420687-diaporama.jpg', 'extension': 'jpg', 'fileName': '2019_COS_Pontgibaud_MarchandDeCailloux (1)', 'taille': 430475, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:30.941+0000'}], 'locked': False}, {'identifiant': 12281590, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de cailloux'}, 'legende': {}, 'copyright': {'libelleFr': 'Martin'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/80/134/12420688.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/80/134/12420688-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/80/134/12420688-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/80/134/12420688-diaporama.jpg', 'extension': 'jpg', 'fileName': '2019_COS_Pontgibaud_MarchandDeCailloux (3)', 'taille': 413535, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:30.387+0000'}], 'locked': False}, {'identifiant': 12281591, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Magasin'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/81/134/12420689.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/81/134/12420689-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/81/134/12420689-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/81/134/12420689-diaporama.jpg', 'extension': 'jpg', 'fileName': '1966258', 'taille': 547145, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:33.476+0000'}], 'locked': False}, {'identifiant': 12281592, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Magasin'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/82/134/12420690.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/82/134/12420690-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/82/134/12420690-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/82/134/12420690-diaporama.jpg', 'extension': 'jpg', 'fileName': '6775442', 'taille': 823905, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:32.749+0000'}], 'locked': False}, {'identifiant': 12281593, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Plaque'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/83/134/12420691.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/83/134/12420691-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/83/134/12420691-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/83/134/12420691-diaporama.jpg', 'extension': 'jpg', 'fileName': '8860707', 'taille': 615964, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:32.112+0000'}], 'locked': False}, {'identifiant': 12281594, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': "Tableaux avec inclusions d'argent"}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/84/134/12420692.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/84/134/12420692-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/84/134/12420692-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/84/134/12420692-diaporama.jpg', 'extension': 'jpg', 'fileName': '8860708', 'taille': 574710, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:31.523+0000'}], 'locked': False}, {'identifiant': 12281595, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Numéros de rue'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/85/134/12420693.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/85/134/12420693-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/85/134/12420693-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/85/134/12420693-diaporama.jpg', 'extension': 'jpg', 'fileName': '8860709', 'taille': 585601, 'hauteur': 924, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:36.066+0000'}], 'locked': False}, {'identifiant': 12281596, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Plaques de maison'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/86/134/12420694.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/86/134/12420694-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/86/134/12420694-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/86/134/12420694-diaporama.jpg', 'extension': 'jpg', 'fileName': '8860710', 'taille': 470438, 'hauteur': 732, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:35.468+0000'}], 'locked': False}, {'identifiant': 12281597, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Dessous de verres'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/87/134/12420695.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/87/134/12420695-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/87/134/12420695-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/87/134/12420695-diaporama.jpg', 'extension': 'jpg', 'fileName': '8860711', 'taille': 579663, 'hauteur': 1050, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:34.824+0000'}], 'locked': False}, {'identifiant': 12281598, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Bijoux'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/88/134/12420696.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/88/134/12420696-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/88/134/12420696-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/88/134/12420696-diaporama.jpg', 'extension': 'jpg', 'fileName': '8860713', 'taille': 644744, 'hauteur': 1600, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:49:34.193+0000'}], 'locked': False}, {'identifiant': 12328701, 'link': False, 'type': 'IMAGE', 'nom': {'libelleFr': 'Marchand de Cailloux'}, 'legende': {'libelleFr': 'Plaque lave émaillée'}, 'copyright': {'libelleFr': 'Martin K.'}, 'traductionFichiers': [{'locale': 'fr', 'url': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/107/92/12475499.jpg', 'urlListe': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/107/92/12475499-liste.jpg', 'urlFiche': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/107/92/12475499-fiche.jpg', 'urlDiaporama': 'http://static.apidae-tourisme.com/filestore/objets-touristiques/images/107/92/12475499-diaporama.jpg', 'extension': 'jpg', 'fileName': '12420697', 'taille': 265136, 'hauteur': 900, 'largeur': 1200, 'lastModifiedDate': '2022-02-02T12:53:19.168+0000'}], 'locked': False}]} diff --git a/geotrek/common/tests/factories.py b/geotrek/common/tests/factories.py index a4a1635bbe..4d81c973e4 100644 --- a/geotrek/common/tests/factories.py +++ b/geotrek/common/tests/factories.py @@ -1,50 +1,12 @@ import factory -import os -import tempfile -from zipfile import ZipFile - from geotrek.authent.tests.factories import UserFactory from geotrek.common.models import Attachment from geotrek.common.utils.testdata import (dummy_filefield_as_sequence, get_dummy_uploaded_image, - get_dummy_uploaded_file) + get_dummy_uploaded_file, get_dummy_uploaded_image_svg) from .. import models -from geotrek.common.management.commands.sync_rando import Command - -from django.conf import settings -from django.test.client import RequestFactory - - -class FakeSyncCommand(Command): - categories = '1' - verbosity = 2 - host = 'localhost:8000' - secure = True - with_infrastructures = True - with_signages = True - with_events = True - rando_url = 'localhost:3000' - - def __init__(self, portal='', source='', skip_dem=False, skip_pdf=False, skip_profile_png=False): - super().__init__(stdout=None, stderr=None, no_color=False, force_color=False) - self.dst_root = settings.TMP_DIR - self.temporary_directory = tempfile.TemporaryDirectory(dir=settings.VAR_DIR) - self.tmp_root = self.temporary_directory.name - zipname = os.path.join('zip', 'tiles', 'global.zip') - global_file = os.path.join(self.tmp_root, zipname) - dirname = os.path.dirname(global_file) - if not os.path.exists(dirname): - os.makedirs(dirname) - self.zipfile = ZipFile(os.path.join(self.tmp_root, 'zip', 'tiles', 'global.zip'), 'w') - self.factory = RequestFactory() - self.source = source - self.portal = portal - self.skip_dem = skip_dem - self.skip_pdf = skip_pdf - self.skip_profile_png = skip_profile_png - class OrganismFactory(factory.django.DjangoModelFactory): class Meta: @@ -85,6 +47,21 @@ class Meta: legend = factory.Sequence("Legend {0}".format) +class AttachmentImageFactory(AttachmentFactory): + attachment_file = factory.django.ImageField() + is_image = True + + class Meta: + model = Attachment + + +class AttachmentPictoSVGFactory(AttachmentFactory): + attachment_file = get_dummy_uploaded_image_svg() + + class Meta: + model = Attachment + + class ThemeFactory(factory.django.DjangoModelFactory): class Meta: model = models.Theme diff --git a/geotrek/common/tests/test_embed_videos.py b/geotrek/common/tests/test_embed_videos.py new file mode 100644 index 0000000000..71f04eb4e8 --- /dev/null +++ b/geotrek/common/tests/test_embed_videos.py @@ -0,0 +1,37 @@ +from django.test import TestCase +from embed_video.backends import UnknownIdException, detect_backend + +from geotrek.common.embed.backends import DailymotionBackend + + +class DailymotionBackendTestCase(TestCase): + urls = ( + ("https://www.dailymotion.com/video/x8uw0jq", "x8uw0jq"), + ("http://www.dailymotion.com/video/x8uw0jq", "x8uw0jq"), + ("https://dailymotion.com/video/x8uw0jq", "x8uw0jq"), + ("http://dailymotion.com/video/x8uw0jq", "x8uw0jq"), + ("https://dai.ly/x8uw09k", "x8uw09k"), + ("http://dai.ly/x8uw09k", "x8uw09k"), + ) + + instance = DailymotionBackend + + def test_detect(self): + for url in self.urls: + backend = detect_backend(url[0]) + self.assertIsInstance(backend, self.instance) + + def test_code(self): + for url in self.urls: + backend = self.instance(url[0]) + self.assertEqual(backend.code, url[1]) + + def test_youtube_keyerror(self): + """Test for issue #7""" + backend = self.instance("https://www.toto.com/watch?id=5") + self.assertRaises(UnknownIdException, backend.get_code) + + def test_thumbnail(self): + for url in self.urls: + backend = self.instance(url[0]) + self.assertIn(url[1], backend.thumbnail) diff --git a/geotrek/common/tests/test_serializers.py b/geotrek/common/tests/test_serializers.py index a91d6b1649..5d249b73a5 100644 --- a/geotrek/common/tests/test_serializers.py +++ b/geotrek/common/tests/test_serializers.py @@ -1,4 +1,3 @@ - from django.test import TestCase from geotrek.common.serializers import HDViewPointGeoJSONSerializer diff --git a/geotrek/common/tests/test_sync.py b/geotrek/common/tests/test_sync.py deleted file mode 100644 index fe97383f69..0000000000 --- a/geotrek/common/tests/test_sync.py +++ /dev/null @@ -1,408 +0,0 @@ -import errno -import os -import json -from landez.sources import DownloadError -from unittest import mock -import shutil -from io import StringIO -import zipfile - -from django.conf import settings -from django.test import TestCase -from django.contrib.gis.geos import LineString -from django.core import management -from django.core.management.base import CommandError -from django.http import HttpResponse, StreamingHttpResponse -from django.test.utils import override_settings - -from geotrek.common.tests.factories import FileTypeFactory, RecordSourceFactory, TargetPortalFactory, AttachmentFactory, ThemeFactory -from geotrek.common.utils.testdata import get_dummy_uploaded_image -from geotrek.core.tests.factories import PathFactory -from geotrek.infrastructure.tests.factories import InfrastructureFactory -from geotrek.sensitivity.tests.factories import SensitiveAreaFactory, SportPracticeFactory -from geotrek.signage.tests.factories import SignageFactory -from geotrek.tourism.tests.factories import InformationDeskFactory, TouristicContentFactory, TouristicEventFactory -from geotrek.trekking.tests.factories import TrekFactory, TrekWithPublishedPOIsFactory -from geotrek.trekking import models as trekking_models - - -class VarTmpTestCase(TestCase): - def setUp(self): - if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')): - shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')) - - def tearDown(self): - if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')): - shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')) - - -class SyncRandoTilesTest(VarTmpTestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando')): - shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando')) - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - @mock.patch('landez.TilesManager.tile', return_value=b'I am a png') - def test_tiles(self, mock_tileslist, mock_tiles): - output = StringIO() - - p = PathFactory.create(geom=LineString((0, 0), (0, 10))) - trek_multi = TrekFactory.create(published=True, paths=[(p, 0, 0.1), (p, 0.2, 0.3)]) - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - languages='en', stdout=output) - zfile = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', 'global.zip')) - for finfo in zfile.infolist(): - ifile_global = zfile.open(finfo) - if ifile_global.name.startswith('tiles/'): - self.assertEqual(ifile_global.readline(), b'I am a png') - zfile_trek = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', '{}.zip'.format(trek_multi.pk))) - for finfo in zfile_trek.infolist(): - ifile_trek = zfile_trek.open(finfo) - if ifile_trek.name.startswith('tiles/'): - self.assertEqual(ifile_trek.readline(), b'I am a png') - self.assertIn("tiles/global.zip", output.getvalue()) - self.assertIn("tiles/{pk}.zip".format(pk=trek_multi.pk), output.getvalue()) - - @mock.patch('landez.TilesManager.tile', return_value='Error') - @mock.patch('landez.TilesManager.tileslist', return_value=[(9, 258, 199)]) - def test_tile_fail(self, mock_tileslist, mock_tiles): - mock_tiles.side_effect = DownloadError - output = StringIO() - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - languages='en', stdout=output) - zfile = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', 'global.zip')) - for finfo in zfile.infolist(): - ifile = zfile.open(finfo) - self.assertEqual(ifile.read(), b'I am a png') - self.assertIn("zip/tiles/global.zip", output.getvalue()) - - @override_settings(MOBILE_TILES_URL=['http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png']) - @mock.patch('landez.TilesManager.tile', return_value='Error') - @mock.patch('landez.TilesManager.tileslist', return_value=[(9, 258, 199)]) - def test_multiple_tiles(self, mock_tileslist, mock_tiles): - mock_tiles.side_effect = DownloadError - output = StringIO() - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - languages='en', stdout=output) - zfile = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', 'global.zip')) - for finfo in zfile.infolist(): - ifile = zfile.open(finfo) - self.assertEqual(ifile.read(), b'I am a png') - self.assertIn("zip/tiles/global.zip", output.getvalue()) - - @override_settings(MOBILE_TILES_URL='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png') - @mock.patch('landez.TilesManager.tile', return_value='Error') - @mock.patch('landez.TilesManager.tileslist', return_value=[(9, 258, 199)]) - def test_tiles_url_str(self, mock_tileslist, mock_tiles): - mock_tiles.side_effect = DownloadError - output = StringIO() - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - languages='en', stdout=output) - zfile = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', 'global.zip')) - for finfo in zfile.infolist(): - ifile = zfile.open(finfo) - self.assertEqual(ifile.read(), b'I am a png') - self.assertIn("zip/tiles/global.zip", output.getvalue()) - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - @mock.patch('landez.TilesManager.tile', return_value=b'I am a png') - @mock.patch('landez.TilesManager.tileslist', return_value=[(9, 258, 199)]) - def test_tiles_with_treks(self, mock_tileslist, mock_tiles, mock_prepare): - output = StringIO() - trek = TrekFactory.create(published=True) - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - languages='en', stdout=output) - zfile = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', 'global.zip')) - for finfo in zfile.infolist(): - ifile = zfile.open(finfo) - self.assertEqual(ifile.read(), b'I am a png') - self.assertIn("zip/tiles/global.zip", output.getvalue()) - zfile_trek = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', '{pk}.zip'.format(pk=trek.pk))) - for finfo in zfile_trek.infolist(): - ifile_trek = zfile_trek.open(finfo) - self.assertEqual(ifile_trek.read(), b'I am a png') - self.assertIn("zip/tiles/{pk}.zip".format(pk=trek.pk), output.getvalue()) - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - @mock.patch('landez.TilesManager.tile', return_value=b'I am a png') - @mock.patch('landez.TilesManager.tileslist', return_value=[(9, 258, 199)]) - def test_tiles_with_treks_source_portal(self, mock_tileslist, mock_tiles, mock_prepare): - output = StringIO() - self.source = RecordSourceFactory() - self.portal = TargetPortalFactory() - trek = TrekFactory.create(published=True, sources=(self.source,), portals=(self.portal,)) - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - source=self.source.name, portal=self.portal.name, languages='fr', - verbosity=2, stdout=output, stderr=StringIO()) - zfile = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', 'global.zip')) - for finfo in zfile.infolist(): - ifile = zfile.open(finfo) - self.assertEqual(ifile.read(), b'I am a png') - self.assertIn("zip/tiles/global.zip", output.getvalue()) - zfile_trek = zipfile.ZipFile(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'zip', 'tiles', '{pk}.zip'.format(pk=trek.pk))) - for finfo in zfile_trek.infolist(): - ifile_trek = zfile_trek.open(finfo) - self.assertEqual(ifile_trek.read(), b'I am a png') - self.assertIn("zip/tiles/{pk}.zip".format(pk=trek.pk), output.getvalue()) - - -class SyncRandoFailTest(VarTmpTestCase): - def test_fail_directory_not_empty(self): - os.makedirs(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'other')) - with self.assertRaisesRegex(CommandError, "Destination directory contains extra data"): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, verbosity=2) - - def test_fail_url_ftp(self): - with self.assertRaisesRegex(CommandError, "url parameter should start with http:// or https://"): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='ftp://localhost:8000', - skip_tiles=True, languages='en', verbosity=2) - - def test_language_not_in_db(self): - with self.assertRaisesRegex(CommandError, - r"Language cat doesn't exist. Select in these one : \['en', 'es', 'fr', 'it'\]"): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, languages='cat', verbosity=2) - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - def test_attachments_missing_from_disk(self, mocke): - mocke.side_effect = Exception() - trek_1 = TrekWithPublishedPOIsFactory.create(published_fr=True) - attachment = AttachmentFactory(content_object=trek_1, attachment_file=get_dummy_uploaded_image()) - os.remove(attachment.attachment_file.path) - with self.assertRaisesRegex(CommandError, 'Some errors raised during synchronization.'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, languages='fr', verbosity=2, stdout=StringIO(), stderr=StringIO()) - self.assertFalse(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'mobile', 'nolang', 'media', 'trekking_trek'))) - - @mock.patch('os.mkdir') - def test_fail_sync_permission_denied(self, mkdir): - mkdir.side_effect = OSError(errno.EACCES, 'Permission Denied') - with self.assertRaisesRegex(OSError, r'\[Errno 13\] Permission Denied'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, verbosity=2) - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - @mock.patch('geotrek.trekking.views.TrekAPIViewSet.as_view') - def test_response_500(self, mock_view, mocke_map_image): - error = StringIO() - mock_view.return_value.return_value = HttpResponse(status=500) - TrekWithPublishedPOIsFactory.create(published_fr=True) - with self.assertRaisesRegex(CommandError, 'Some errors raised during synchronization.'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, verbosity=2, stdout=StringIO(), stderr=error) - self.assertIn("failed (HTTP 500)", error.getvalue()) - - @mock.patch('geotrek.trekking.views.TrekAPIViewSet.list') - def test_response_view_exception(self, mocke): - output = StringIO() - mocke.side_effect = Exception('This is a test') - TrekWithPublishedPOIsFactory.create(published_fr=True) - with self.assertRaisesRegex(CommandError, 'Some errors raised during synchronization.'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - portal='portal', skip_pdf=True, - skip_tiles=True, languages='fr', verbosity=2, stdout=output) - self.assertIn("failed (This is a test)", output.getvalue()) - - @override_settings(DEBUG=True) - @mock.patch('geotrek.trekking.views.TrekAPIViewSet.list') - def test_response_view_exception_with_debug(self, mocke): - output = StringIO() - mocke.side_effect = ValueError('This is a test') - TrekWithPublishedPOIsFactory.create(published_fr=True) - with self.assertRaisesRegex(ValueError, 'This is a test'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - portal='portal', skip_pdf=True, - skip_tiles=True, languages='fr', verbosity=2, stdout=output) - self.assertIn("failed (This is a test)", output.getvalue()) - - def test_sync_fail_src_file_not_exist(self): - output = StringIO() - theme = ThemeFactory.create() - theme.pictogram = "other" - theme.save() - with self.assertRaisesRegex(CommandError, 'Some errors raised during synchronization.'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, languages='fr', verbosity=2, stdout=output, stderr=StringIO()) - self.assertIn("file does not exist", output.getvalue()) - - -class SyncTest(VarTmpTestCase): - @classmethod - def setUpTestData(cls): - cls.trek = TrekWithPublishedPOIsFactory.create(published=True) - - def test_sync_multiple_time(self): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', skip_tiles=True, languages='en', - skip_pdf=True, verbosity=2, stdout=StringIO()) - with open(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'treks.geojson'), 'r') as f: - treks = json.load(f) - - # only 2 treks in Portal B + 1 without portal specified - self.assertEqual(len(treks['features']), 1) - - # portal A and B - output = StringIO() - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', skip_tiles=True, languages='en', - skip_pdf=True, verbosity=2, stdout=output) - self.assertIn("unchanged", output.getvalue()) - - @override_settings(THUMBNAIL_COPYRIGHT_FORMAT='*' * 300) - def test_sync_pictures_long_title_legend_author(self): - with mock.patch('geotrek.trekking.models.Trek.prepare_map_image'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, skip_pdf=True, skip_dem=True, skip_profile_png=True, - languages='en', verbosity=2, stdout=StringIO()) - with open(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'treks.geojson'), 'r') as f: - treks = json.load(f) - self.assertEqual(len(treks['features']), - trekking_models.Trek.objects.filter(published=True).count()) - - def test_sync_2028(self): - old_description = self.trek.description - self.trek.description = 'toto\u2028tata' - self.trek.save() - - with mock.patch('geotrek.trekking.models.Trek.prepare_map_image'): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - skip_tiles=True, skip_pdf=True, skip_dem=True, skip_profile_png=True, - languages='en', verbosity=2, stdout=StringIO()) - with open(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'treks.geojson'), 'r') as f: - treks = json.load(f) - # \u2028 is translated to \n - self.assertEqual(treks['features'][0]['properties']['description'], 'toto\ntata') - self.trek.description = old_description - self.trek.save() - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - @override_settings(ONLY_EXTERNAL_PUBLIC_PDF=True) - def test_only_external_public_pdf(self, trek): - output = StringIO() - trek = TrekFactory.create(published=True, ) - filetype_topoguide = FileTypeFactory.create(type='Topoguide') - AttachmentFactory.create(content_object=trek, attachment_file=get_dummy_uploaded_image(), - filetype=filetype_topoguide) - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - skip_pdf=False, skip_tiles=True, stdout=output) - self.assertFalse(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'treks', str(self.trek.pk), '%s.pdf' % self.trek.slug))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'treks', str(trek.pk), '%s.pdf' % trek.slug))) - - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - def test_sync_pdf_all_languages(self, trek): - output = StringIO() - trek = TrekFactory.create(published_it=True, published_en=True, published_fr=True, name_fr='FR', name_en='EN', - name_it="IT") - trek_2 = TrekFactory.create(published_it=True, published_en=True, published_fr=True, name_fr='FR_2', - name_en='EN_2', name_it="IT_2") - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', verbosity=2, - skip_pdf=False, skip_tiles=True, stdout=output) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'treks', str(trek.pk), '%s.pdf' % trek.slug))) - self.assertFalse(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek.pk), '%s.pdf' % trek.slug))) - self.assertFalse(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek.pk), '%s.pdf' % trek.slug))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek.pk), 'it.pdf'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek.pk), 'fr.pdf'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek.pk), 'it.gpx'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek.pk), 'fr.gpx'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek.pk), 'it.kml'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek.pk), 'fr.kml'))) - - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek_2.pk), 'it_2.pdf'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek_2.pk), 'fr_2.pdf'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek_2.pk), 'it_2.gpx'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek_2.pk), 'fr_2.gpx'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'it', 'treks', str(trek_2.pk), 'it_2.kml'))) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek_2.pk), 'fr_2.kml'))) - - -class SyncComplexTest(VarTmpTestCase): - @classmethod - def setUpTestData(cls): - cls.information_desks = InformationDeskFactory.create() - cls.trek = TrekWithPublishedPOIsFactory.create(published=True) - if settings.TREKKING_TOPOLOGY_ENABLED: - InfrastructureFactory.create(paths=[(cls.trek.paths.first(), 0, 0)], name="INFRA_1") - SignageFactory.create(paths=[(cls.trek.paths.first(), 0, 0)], name="SIGNA_1") - else: - InfrastructureFactory.create(geom='SRID=2154;POINT(700000 6600000)', name="INFRA_1") - SignageFactory.create(geom='SRID=2154;POINT(700000 6600000)', name="SIGNA_1") - area = SensitiveAreaFactory.create(published=True) - area.species.practices.add(SportPracticeFactory.create(name='Terrestre')) - area.save() - cls.touristic_content = TouristicContentFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.touristic_event = TouristicEventFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.attachment_touristic_content = AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - cls.attachment_touristic_event = AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - cls.touristic_content_without_attachment = TouristicContentFactory( - geom='SRID=%s;POINT(700002 6600002)' % settings.SRID, published=True) - cls.touristic_event_without_attachment = TouristicEventFactory( - geom='SRID=%s;POINT(700002 6600002)' % settings.SRID, published=True) - - def get_coordinates(self, geojsonfilename): - with open(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', geojsonfilename), 'r') as f: - geojsonfile = json.load(f) - if geojsonfile['features']: - coordinates = geojsonfile['features'][0]['geometry']['coordinates'] - return coordinates - return None - - def test_sync_geom_4326(self): - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', - with_signages=True, with_infrastructures=True, with_dives=True, - skip_tiles=True, skip_pdf=True, languages='en', verbosity=2, - content_categories="1", with_events=True, stdout=StringIO()) - geojson_files = [ - 'infrastructures.geojson', - 'touristiccontents.geojson', - 'touristicevents.geojson', - 'sensitiveareas.geojson', - 'signages.geojson', - 'services.geojson', - ] - for geojsonfilename in geojson_files: - with self.subTest(line=geojsonfilename): - coordinates = self.get_coordinates(geojsonfilename) - if coordinates: - if isinstance(coordinates[0], float): - self.assertTrue(coordinates[0] < 90) - elif isinstance(coordinates[0][0], float): - self.assertTrue(coordinates[0][0] < 90) - elif isinstance(coordinates[0][0][0], float): - self.assertTrue(coordinates[0][0][0] < 90) - - @override_settings(SPLIT_TREKS_CATEGORIES_BY_PRACTICE=False, SPLIT_DIVES_CATEGORIES_BY_PRACTICE=False) - def test_sync_with_multipolygon_sensitive_area(self): - area = SensitiveAreaFactory.create(geom='MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))', published=True) - area.species.practices.add(SportPracticeFactory.create(name='Terrestre')) - area.save() - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), with_signages=True, with_infrastructures=True, - with_dives=True, with_events=True, content_categories="1", url='http://localhost:8000', - skip_tiles=True, skip_pdf=True, verbosity=2, languages='en', stdout=StringIO()) - with open(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'en', 'sensitiveareas.geojson'), 'r') as f: - area = json.load(f) - # there are 2 areas - self.assertEqual(len(area['features']), 2) - - @override_settings(SPLIT_TREKS_CATEGORIES_BY_PRACTICE=False, SPLIT_DIVES_CATEGORIES_BY_PRACTICE=False) - def test_sync_picture_missing_from_disk(self): - os.remove(self.information_desks.photo.path) - output = StringIO() - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), with_signages=True, with_infrastructures=True, - with_dives=True, with_events=True, content_categories="1", url='http://localhost:8000', - skip_tiles=True, skip_pdf=True, languages='en', verbosity=2, stdout=output) - self.assertIn('Done', output.getvalue()) - - @mock.patch('geotrek.trekking.views.TrekAPIViewSet.list') - def test_streaminghttpresponse(self, mocke): - output = StringIO() - mocke.return_value = StreamingHttpResponse() - trek = TrekWithPublishedPOIsFactory.create(published_fr=True) - management.call_command('sync_rando', os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), url='http://localhost:8000', skip_pdf=True, - skip_tiles=True, languages='fr', verbosity=2, stdout=output) - self.assertTrue(os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync', 'api', 'fr', 'treks', str(trek.pk), 'profile.png'))) diff --git a/geotrek/common/tests/test_tags.py b/geotrek/common/tests/test_tags.py new file mode 100644 index 0000000000..551b16c8b4 --- /dev/null +++ b/geotrek/common/tests/test_tags.py @@ -0,0 +1,25 @@ +from django.test import TestCase + +from geotrek.common.templatetags.geotrek_tags import duration + + +class DurationTagTestCase(TestCase): + def test_duration_lt_1h(self): + self.assertEqual("15 min", duration(0.25)) + self.assertEqual("30 min", duration(0.5)) + + def test_duration_lt_1day(self): + self.assertEqual("1 h", duration(1)) + self.assertEqual("1 h 45", duration(1.75)) + self.assertEqual("3 h 30", duration(3.5)) + self.assertEqual("4 h", duration(4)) + self.assertEqual("6 h", duration(6)) + self.assertEqual("10 h", duration(10)) + + def test_duration_gte_1day(self): + self.assertEqual("1 day", duration(24)) + self.assertEqual("2 days", duration(32)) + self.assertEqual("2 days", duration(48)) + self.assertEqual("3 days", duration(49)) + self.assertEqual("8 days", duration(24 * 8)) + self.assertEqual("9 days", duration(24 * 9)) diff --git a/geotrek/common/tests/test_thumbnail_processors.py b/geotrek/common/tests/test_thumbnail_processors.py new file mode 100644 index 0000000000..157e1e5462 --- /dev/null +++ b/geotrek/common/tests/test_thumbnail_processors.py @@ -0,0 +1,36 @@ +from django.test import TestCase +from PIL import Image + +from geotrek.common.thumbnail_processors import add_watermark + + +class AddWatermarkTest(TestCase): + def test_add_watermark_with_text_and_size(self): + image = Image.new("RGB", (60, 30), color="red") + kwargs = {"TEXT": "Test", "SIZE_WATERMARK": 10} + result = add_watermark(image, **kwargs) + self.assertIsInstance(result, Image.Image) + + def test_add_watermark_without_text(self): + image = Image.new("RGB", (60, 30), color="red") + kwargs = {"TEXT": "", "SIZE_WATERMARK": 10} + result = add_watermark(image, **kwargs) + self.assertEqual(result, image) + + def test_add_watermark_with_text_and_without_size(self): + image = Image.new("RGB", (60, 30), color="red") + kwargs = {"TEXT": "Test", "SIZE_WATERMARK": None} + with self.assertRaises(TypeError): + add_watermark(image, **kwargs) + + def test_add_watermark_with_size_and_without_text(self): + image = Image.new("RGB", (60, 30), color="red") + kwargs = {"TEXT": None, "SIZE_WATERMARK": 10} + result = add_watermark(image, **kwargs) + self.assertEqual(result, image) + + def test_add_watermark_without_text_and_size(self): + image = Image.new("RGB", (60, 30), color="red") + kwargs = {"TEXT": None, "SIZE_WATERMARK": None} + result = add_watermark(image, **kwargs) + self.assertEqual(result, image) diff --git a/geotrek/common/tests/test_views.py b/geotrek/common/tests/test_views.py index 5a63bb1968..ef0e896eec 100644 --- a/geotrek/common/tests/test_views.py +++ b/geotrek/common/tests/test_views.py @@ -6,7 +6,7 @@ from unittest import mock from django.conf import settings -from django.contrib.auth.models import Permission, User +from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.contrib.gis.geos import Point from django.core.files import File @@ -14,8 +14,7 @@ from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse -from geotrek.common.views import HDViewPointAPIViewSet -from mapentity.tests.factories import SuperUserFactory, UserFactory +from mapentity.tests.factories import UserFactory from mapentity.views.generic import MapEntityList import geotrek.trekking.parsers # noqa @@ -23,7 +22,7 @@ from geotrek.common.mixins.views import CustomColumnsMixin from geotrek.common.models import FileType, HDViewPoint from geotrek.common.parsers import Parser -from geotrek.common.tasks import import_datas, launch_sync_rando +from geotrek.common.tasks import import_datas from geotrek.common.tests.factories import (HDViewPointFactory, LicenseFactory, TargetPortalFactory) from geotrek.common.utils.testdata import get_dummy_uploaded_image @@ -107,7 +106,7 @@ def test_trek_document_wrong_portal(self, mock_request_get): class ViewsTest(TestCase): @classmethod def setUpTestData(cls): - cls.user = UserFactory.create(username='homer', password='dooh') + cls.user = UserFactory.create() def setUp(self): self.client.force_login(user=self.user) @@ -142,7 +141,7 @@ class MissingColumns(CustomColumnsMixin, MapEntityList): class ViewsImportTest(TestCase): @classmethod def setUpTestData(cls): - cls.user = UserFactory.create(username='homer', password='dooh') + cls.user = UserFactory.create() def setUp(self): self.client.force_login(user=self.user) @@ -286,124 +285,6 @@ def test_import_from_web_good_parser(self): "is not one of the available choices.".format(real_key=real_key)) -class SyncRandoViewTest(TestCase): - @classmethod - def setUpTestData(cls): - cls.super_user = SuperUserFactory.create(username='admin', password='super') - cls.simple_user = User.objects.create_user(username='homer', password='doooh') - - def setUp(self): - if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')): - shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')) - - def test_get_sync_superuser(self): - self.client.login(username='admin', password='super') - response = self.client.get(reverse('common:sync_randos_view')) - self.assertEqual(response.status_code, 200) - - def test_post_sync_superuser(self): - """ - test if sync can be launched by superuser post - """ - self.client.login(username='admin', password='super') - response = self.client.post(reverse('common:sync_randos'), data={}) - self.assertRedirects(response, '/commands/syncview') - - def test_get_sync_simpleuser(self): - self.client.login(username='homer', password='doooh') - response = self.client.get(reverse('common:sync_randos_view')) - self.assertRedirects(response, '/login/?next=/commands/syncview') - - def test_post_sync_simpleuser(self): - """ - test if sync can be launched by simple user post - """ - self.client.login(username='homer', password='doooh') - response = self.client.post(reverse('common:sync_randos'), data={}) - self.assertRedirects(response, '/login/?next=/commands/sync') - - def test_get_sync_states_superuser(self): - self.client.login(username='admin', password='super') - response = self.client.post(reverse('common:sync_randos_state'), data={}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.content, b'[]') - - def test_get_sync_states_simpleuser(self): - self.client.login(username='homer', password='doooh') - response = self.client.post(reverse('common:sync_randos_state'), data={}) - self.assertRedirects(response, '/login/?next=/commands/statesync/') - - @mock.patch('sys.stdout', new_callable=StringIO) - @override_settings(CELERY_ALWAYS_EAGER=False, - SYNC_RANDO_ROOT=os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), - SYNC_RANDO_OPTIONS={'url': 'http://localhost:8000', - 'skip_tiles': True, 'skip_pdf': True, - 'skip_dem': True, 'skip_profile_png': True}) - def test_get_sync_rando_states_superuser_with_sync_rando(self, mocked_stdout): - if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')): - shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')) - self.client.login(username='admin', password='super') - launch_sync_rando.apply() - response = self.client.post(reverse('common:sync_randos_state'), data={}) - self.assertEqual(response.status_code, 200) - self.assertIn(b'"infos": "Sync ended"', response.content) - - @mock.patch('sys.stdout', new_callable=StringIO) - @mock.patch('geotrek.common.management.commands.sync_rando.Command.handle', return_value=None, - side_effect=Exception('This is a test')) - @override_settings(CELERY_ALWAYS_EAGER=False, - SYNC_RANDO_ROOT=os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), - SYNC_RANDO_OPTIONS={'url': 'http://localhost:8000', - 'skip_tiles': True, 'skip_pdf': True, - 'skip_dem': True, 'skip_profile_png': True}) - def test_get_sync_rando_states_superuser_with_sync_mobile_fail(self, mocked_stdout, command): - self.client.login(username='admin', password='super') - launch_sync_rando.apply() - response = self.client.post(reverse('common:sync_randos_state'), data={}) - self.assertEqual(response.status_code, 200) - self.assertIn(b'"exc_message": "This is a test"', response.content) - - @mock.patch('sys.stdout', new_callable=StringIO) - @mock.patch('geotrek.trekking.models.Trek.prepare_map_image') - @mock.patch('landez.TilesManager.tile', return_value=b'I am a png') - @override_settings(SYNC_RANDO_ROOT=os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync'), - SYNC_RANDO_OPTIONS={'url': 'http://localhost:8000', 'skip_tiles': False, - 'skip_pdf': False, - 'skip_dem': False, 'skip_profile_png': False}) - def test_launch_sync_rando(self, mock_tile, mock_map_image, mocked_stdout): - task = launch_sync_rando.apply() - log = mocked_stdout.getvalue() - self.assertIn("Done", log) - self.assertEqual(task.status, "SUCCESS") - - @mock.patch('geotrek.common.management.commands.sync_rando.Command.handle', return_value=None, - side_effect=Exception('This is a test')) - @mock.patch('sys.stdout', new_callable=StringIO) - def test_launch_sync_rando_fail(self, mocked_stdout, command): - task = launch_sync_rando.apply() - log = mocked_stdout.getvalue() - self.assertNotIn("Done", log) - self.assertNotIn('Sync ended', log) - self.assertEqual(task.status, "FAILURE") - - @mock.patch('geotrek.common.management.commands.sync_rando.Command.handle', return_value=None, - side_effect=Exception('This is a test')) - @override_settings(SYNC_RANDO_ROOT=os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')) - @mock.patch('sys.stdout', new_callable=StringIO) - def test_launch_sync_rando_no_rando_root(self, mocked_stdout, command): - if os.path.exists(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')): - shutil.rmtree(os.path.join(settings.TMP_DIR, 'sync_rando', 'tmp_sync')) - task = launch_sync_rando.apply() - log = mocked_stdout.getvalue() - self.assertNotIn("Done", log) - self.assertNotIn('Sync rando ended', log) - self.assertEqual(task.status, "FAILURE") - - 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): @@ -528,14 +409,6 @@ def test_annotate_view(self): 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) diff --git a/geotrek/common/thumbnail_processors.py b/geotrek/common/thumbnail_processors.py index 5f1dfe3701..c0fab36d37 100644 --- a/geotrek/common/thumbnail_processors.py +++ b/geotrek/common/thumbnail_processors.py @@ -1,5 +1,4 @@ -from PIL import ImageDraw -from PIL import ImageFont +from PIL import ImageDraw, ImageFont def add_watermark(image, **kwargs): diff --git a/geotrek/common/urls.py b/geotrek/common/urls.py index 44d68f579e..60c340868c 100644 --- a/geotrek/common/urls.py +++ b/geotrek/common/urls.py @@ -26,14 +26,6 @@ class LangConverter(converters.StringConverter): name="import_update_json", ), path("commands/import", views.import_view, name="import_dataset"), - path("commands/sync", views.SyncRandoRedirect.as_view(), name="sync_randos"), - path("commands/syncview", views.sync_view, name="sync_randos_view"), - path("commands/statesync/", views.sync_update_json, name="sync_randos_state"), - path( - "api//themes.json", - views.ThemeViewSet.as_view({"get": "list"}), - name="themes_json", - ), path( "hdviewpoint/annotate/", views.HDViewPointAnnotate.as_view(), diff --git a/geotrek/common/views.py b/geotrek/common/views.py index 73c8962f11..f6b4852bce 100644 --- a/geotrek/common/views.py +++ b/geotrek/common/views.py @@ -17,22 +17,20 @@ 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 (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 import timezone 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.defaults import page_not_found from django.views.decorators.http import require_http_methods, require_POST -from django.views.generic import RedirectView, TemplateView, UpdateView, View +from django.views.generic import TemplateView, UpdateView, View from django_celery_results.models import TaskResult from django_large_image.rest import LargeImageFileDetailMixin from geotrek.common.filters import HDViewPointFilterSet @@ -41,16 +39,13 @@ from mapentity.helpers import api_bbox from mapentity.registry import app_settings, registry from mapentity.views import MapEntityList -from modeltranslation.utils import build_localized_fieldname from paperclip import settings as settings_paperclip from paperclip.views import _handle_attachment_form 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 @@ -58,17 +53,12 @@ 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 + ImportDatasetFormWithFile, ImportSuricateForm) +from .mixins.views import BookletMixin, CompletenessMixin, DocumentPortalMixin, DocumentPublicMixin +from .models import AccessibilityAttachment, HDViewPoint 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 .serializers import HDViewPointGeoJSONSerializer, HDViewPointSerializer, HDViewPointAPISerializer +from .tasks import import_datas, import_datas_from_web from .utils import leaflet_bounds from .utils.import_celery import (create_tmp_destination, discover_available_parsers) @@ -83,46 +73,6 @@ def handler404(request, exception, template_name="404.html"): return page_not_found(request, exception, template_name="404.html") -class Meta(MetaMixin, TemplateView): - template_name = 'common/meta.html' - - def get_context_data(self, **kwargs): - lang = self.request.GET.get('lang') - portal = self.request.GET.get('portal') - context = super().get_context_data(**kwargs) - translation.activate(lang) - context['META_DESCRIPTION'] = _('Geotrek is a web app allowing you to prepare your next trekking trip !') - translation.deactivate() - if portal: - try: - target_portal = TargetPortal.objects.get(name=portal) - context['META_DESCRIPTION'] = getattr(target_portal, build_localized_fieldname('description', lang)) - except TargetPortal.DoesNotExist: - pass - - if 'geotrek.trekking' in settings.INSTALLED_APPS: - from geotrek.trekking.models import Trek - context['treks'] = Trek.objects.existing().order_by('pk').filter( - Q(**{build_localized_fieldname('published', lang): True}) - | Q(**{'trek_parents__parent__{published_lang}'.format(published_lang=build_localized_fieldname('published', lang)): True, - 'trek_parents__parent__deleted': False}) - ) - if 'geotrek.tourism' in settings.INSTALLED_APPS: - from geotrek.tourism.models import TouristicContent, TouristicEvent - context['contents'] = TouristicContent.objects.existing().order_by('pk').filter( - **{build_localized_fieldname('published', lang): True} - ) - context['events'] = TouristicEvent.objects.existing().order_by('pk').filter( - **{build_localized_fieldname('published', lang): True} - ) - if 'geotrek.diving' in settings.INSTALLED_APPS: - from geotrek.diving.models import Dive - context['dives'] = Dive.objects.existing().order_by('pk').filter( - **{build_localized_fieldname('published', lang): True} - ) - return context - - class DocumentPublic(DocumentPortalMixin, PublicOrReadPermMixin, DocumentPublicMixin, mapentity_views.MapEntityDocumentWeasyprint): pass @@ -328,24 +278,6 @@ def import_update_json(request): return HttpResponse(json.dumps(results), content_type="application/json") -class ThemeViewSet(viewsets.ModelViewSet): - model = Theme - queryset = Theme.objects.all() - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - serializer_class = ThemeSerializer - - def get_queryset(self): - return super().get_queryset().order_by('id') - - -class ParametersView(View): - def get(request, *args, **kwargs): - response = { - 'geotrek_admin_version': settings.VERSION, - } - return JsonResponse(response) - - class HDViewPointList(MapEntityList): queryset = HDViewPoint.objects.all() filterform = HDViewPointFilterSet @@ -365,15 +297,6 @@ def get_queryset(self): 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') @@ -439,78 +362,6 @@ def last_list(request): return redirect('trekking:trek_list') -@login_required -@user_passes_test(lambda u: u.is_superuser) -def sync_view(request): - """ - Custom views to view / track / launch a sync rando - """ - - return render(request, - 'common/sync_rando.html', - {'form': SyncRandoForm(), }, - ) - - -@login_required -@user_passes_test(lambda u: u.is_superuser) -def sync_update_json(request): - """ - get info from sync_rando celery_task - """ - results = [] - threshold = timezone.now() - timedelta(seconds=60) - for task in TaskResult.objects.filter(date_done__gte=threshold, status='PROGRESS'): - json_results = json.loads(task.result) - if json_results.get('name', '').startswith('geotrek.trekking'): - results.append({ - 'id': task.task_id, - 'result': json_results or {'current': 0, - 'total': 0}, - 'status': task.status - }) - i = celery_app.control.inspect(['celery@geotrek']) - try: - reserved = i.reserved() - except redis.exceptions.ConnectionError: - reserved = None - tasks = [] if reserved is None else reversed(reserved['celery@geotrek']) - for task in tasks: - if task['name'].startswith('geotrek.trekking'): - results.append( - { - 'id': task['id'], - 'result': {'current': 0, 'total': 0}, - 'status': 'PENDING', - } - ) - for task in TaskResult.objects.filter(date_done__gte=threshold, status='FAILURE').order_by('-date_done'): - json_results = json.loads(task.result) - if json_results.get('name', '').startswith('geotrek.trekking'): - results.append({ - 'id': task.task_id, - 'result': json_results or {'current': 0, - 'total': 0}, - 'status': task.status - }) - - return HttpResponse(json.dumps(results), - content_type="application/json") - - -class SyncRandoRedirect(RedirectView): - http_method_names = ['post'] - pattern_name = 'common:sync_randos_view' - - @method_decorator(login_required) - @method_decorator(user_passes_test(lambda u: u.is_superuser)) - def post(self, request, *args, **kwargs): - url = "{scheme}://{host}".format(scheme='https' if self.request.is_secure() else 'http', - host=self.request.get_host()) - self.job = launch_sync_rando.delay(url=url) - return super().post(request, *args, **kwargs) - - class ServeAttachmentAccessibility(View): def get(self, request, *args, **kwargs): diff --git a/geotrek/core/locale/de/LC_MESSAGES/django.po b/geotrek/core/locale/de/LC_MESSAGES/django.po index 769268cabb..db7cb5cba2 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 769268cabb..db7cb5cba2 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 769268cabb..db7cb5cba2 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 7317e2795b..f8e70be02f 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 769268cabb..db7cb5cba2 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/tests/test_filters.py b/geotrek/core/tests/test_filters.py index a5cb417e8c..f11517761c 100644 --- a/geotrek/core/tests/test_filters.py +++ b/geotrek/core/tests/test_filters.py @@ -5,22 +5,22 @@ from django.test import TestCase from geotrek.authent.tests.factories import StructureFactory -from geotrek.land.tests.test_filters import LandFiltersTest - -from geotrek.core.tests.factories import ( - PathFactory, TrailFactory, - CertificationTrailFactory, CertificationLabelFactory -) from geotrek.core.filters import PathFilterSet, TopologyFilter, TrailFilterSet from geotrek.core.models import Topology - -from geotrek.trekking.tests.factories import TrekFactory +from geotrek.land.tests.test_filters import LandFiltersTest from geotrek.trekking.filters import TrekFilterSet +from geotrek.trekking.tests.factories import TrekFactory + +from .factories import ( + CertificationLabelFactory, + CertificationTrailFactory, + PathFactory, + TrailFactory, +) @skipIf(not settings.TREKKING_TOPOLOGY_ENABLED, 'Test with dynamic segmentation only') class PathFilterLandTest(LandFiltersTest): - filterclass = PathFilterSet @@ -167,3 +167,53 @@ def test_filter_trail_on_certification_label(self): """Test trail filter on certification label""" filterset = self.filterset_class({'certification_labels': [self.certification_label_1]}) self.assertEqual(filterset.qs.count(), 1) + + +class PathFilterTest(TestCase): + factory = PathFactory + filterset = PathFilterSet + + def test_provider_filter_without_provider(self): + filter_set = PathFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + path1 = PathFactory.create(provider='my_provider1') + path2 = PathFactory.create(provider='my_provider2') + + filter_set = PathFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(path1, filter_set.qs) + self.assertIn(path2, filter_set.qs) + + +class TrailFilterTest(TestCase): + factory = TrailFactory + filterset = TrailFilterSet + + def test_provider_filter_without_provider(self): + filter_set = TrailFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + trail1 = TrailFactory.create(provider='my_provider1') + trail2 = TrailFactory.create(provider='my_provider2') + + filter_set = TrailFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(trail1, filter_set.qs) + self.assertIn(trail2, filter_set.qs) diff --git a/geotrek/core/tests/test_views.py b/geotrek/core/tests/test_views.py index 4df6202250..1ce5cacda8 100644 --- a/geotrek/core/tests/test_views.py +++ b/geotrek/core/tests/test_views.py @@ -1,32 +1,38 @@ import os import re -from unittest import skipIf, mock +from unittest import mock, skipIf from bs4 import BeautifulSoup from django.conf import settings from django.contrib.auth.models import Permission +from django.contrib.gis.geos import LineString, MultiPolygon, Point, Polygon from django.core.cache import caches -from django.utils.translation import gettext_lazy as _ -from django.urls import reverse -from django.contrib.gis.geos import LineString, Point, Polygon, MultiPolygon from django.test import TestCase - +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from mapentity.tests.factories import UserFactory -from geotrek.common.tests import CommonTest - -from geotrek.authent.tests.factories import PathManagerFactory, StructureFactory from geotrek.authent.tests.base import AuthentFixturesTest - -from geotrek.core.models import Path, Trail, PathSource -from geotrek.core.filters import PathFilterSet, TrailFilterSet - -from geotrek.trekking.tests.factories import POIFactory, TrekFactory, ServiceFactory +from geotrek.authent.tests.factories import PathManagerFactory, StructureFactory +from geotrek.common.tests import CommonTest +from geotrek.core.models import Path, PathSource, Trail +from geotrek.core.tests.factories import ( + ComfortFactory, + PathFactory, + StakeFactory, + TopologyFactory, + TrailFactory, +) from geotrek.infrastructure.tests.factories import InfrastructureFactory -from geotrek.signage.tests.factories import SignageFactory from geotrek.maintenance.tests.factories import InterventionFactory -from geotrek.core.tests.factories import PathFactory, StakeFactory, TrailFactory, ComfortFactory, TopologyFactory -from geotrek.zoning.tests.factories import CityFactory, DistrictFactory, RestrictedAreaFactory, RestrictedAreaTypeFactory +from geotrek.signage.tests.factories import SignageFactory +from geotrek.trekking.tests.factories import POIFactory, ServiceFactory, TrekFactory +from geotrek.zoning.tests.factories import ( + CityFactory, + DistrictFactory, + RestrictedAreaFactory, + RestrictedAreaTypeFactory, +) @skipIf(not settings.TREKKING_TOPOLOGY_ENABLED, 'Test with dynamic segmentation only') @@ -654,7 +660,7 @@ def setUpTestData(cls): def setUp(self): self.client.force_login(self.user) self.gpx_response = self.client.get(reverse('core:path_gpx_detail', args=('en', self.path.pk, 'slug'))) - self.gpx_parsed = BeautifulSoup(self.gpx_response.content, 'lxml') + self.gpx_parsed = BeautifulSoup(self.gpx_response.content, features='xml') self.kml_response = self.client.get(reverse('core:path_kml_detail', args=('en', self.path.pk, 'slug'))) @@ -777,7 +783,7 @@ def test_add_trail_from_existing_topology_does_not_use_pk(self): trail = TrailFactory(offset=3.14) response = self.client.get(Trail.get_add_url() + '?topology=%s' % trail.pk) - soup = bs4.BeautifulSoup(response.content, 'lxml') + soup = bs4.BeautifulSoup(response.content, features='html.parser') textarea_field = soup.find(id="id_topology") self.assertIn('"kind": "TMP"', textarea_field.text) self.assertIn('"offset": 3.14', textarea_field.text) @@ -812,7 +818,7 @@ def setUp(self): self.client.force_login(self.user) self.gpx_response = self.client.get(reverse('core:trail_gpx_detail', args=('en', self.trail.pk, 'slug'))) - self.gpx_parsed = BeautifulSoup(self.gpx_response.content, 'lxml') + self.gpx_parsed = BeautifulSoup(self.gpx_response.content, features='xml') self.kml_response = self.client.get(reverse('core:trail_kml_detail', args=('en', self.trail.pk, 'slug'))) @@ -872,53 +878,3 @@ def test_remove_poi(self): self.assertEqual(poi.deleted, False) self.assertAlmostEqual(1.5, poi.offset) - - -class PathFilterTest(CommonTest, AuthentFixturesTest): - factory = PathFactory - filterset = PathFilterSet - - def test_provider_filter_without_provider(self): - filter_set = PathFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - path1 = PathFactory.create(provider='my_provider1') - path2 = PathFactory.create(provider='my_provider2') - - filter_set = PathFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(path1, filter_set.qs) - self.assertIn(path2, filter_set.qs) - - -class TrailFilterTest(CommonTest, AuthentFixturesTest): - factory = TrailFactory - filterset = TrailFilterSet - - def test_provider_filter_without_provider(self): - filter_set = TrailFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - trail1 = TrailFactory.create(provider='my_provider1') - trail2 = TrailFactory.create(provider='my_provider2') - - filter_set = TrailFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(trail1, filter_set.qs) - self.assertIn(trail2, filter_set.qs) diff --git a/geotrek/core/urls.py b/geotrek/core/urls.py index 741ac558b8..33c001e34a 100644 --- a/geotrek/core/urls.py +++ b/geotrek/core/urls.py @@ -4,7 +4,6 @@ from geotrek.altimetry.urls import AltimetryEntityOptions from geotrek.common.urls import LangConverter -from geotrek.common.views import ParametersView from .models import Path, Trail from .views import ( PathGPXDetail, PathKMLDetail, TrailGPXDetail, TrailKMLDetail, @@ -16,7 +15,6 @@ app_name = 'core' urlpatterns = [ - path('api//parameters.json', ParametersView.as_view(), name='parameters_json'), re_path(r'^path/delete/(?P\d+(,\d+)+)/', MultiplePathDelete.as_view(), name="multiple_path_delete"), path('api//paths//path_.gpx', PathGPXDetail.as_view(), name="path_gpx_detail"), diff --git a/geotrek/diving/helpers_sync.py b/geotrek/diving/helpers_sync.py deleted file mode 100644 index c4a256dbe4..0000000000 --- a/geotrek/diving/helpers_sync.py +++ /dev/null @@ -1,111 +0,0 @@ -from django.conf import settings -from django.db.models import Q - -from modeltranslation.utils import build_localized_fieldname - -import os - -from geotrek.diving import models -from geotrek.diving import views as diving_views - - -if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - from geotrek.sensitivity import views as sensitivity_views -if 'geotrek.tourism' in settings.INSTALLED_APPS: - from geotrek.tourism import views as tourism_views - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - models_picto = [models.Practice, models.Difficulty, models.Level] - self.global_sync.sync_pictograms(lang, models_picto, zipfile=self.global_sync.zipfile) - self.global_sync.sync_geojson(lang, diving_views.DiveAPIViewSet, 'dives.geojson') - - dives = models.Dive.objects.existing().order_by('pk') - dives = dives.filter(**{build_localized_fieldname('published', lang): True}) - - if self.global_sync.source: - dives = dives.filter(source__name__in=self.global_sync.source) - - if self.global_sync.portal: - dives = dives.filter(Q(portal__name=self.global_sync.portal) | Q(portal=None)) - - for dive in dives: - self.sync_detail(lang, dive) - - def sync_pois(self, lang, dive): - params = {'format': 'geojson'} - view = diving_views.DivePOIViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'dives', str(dive.pk), 'pois.geojson') - self.global_sync.sync_view(lang, view, name, params=params, pk=dive.pk) - - def sync_services(self, lang, dive): - view = diving_views.DiveServiceViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'dives', str(dive.pk), 'services.geojson') - self.global_sync.sync_view(lang, view, name, params={'format': 'geojson'}, pk=dive.pk) - - def sync_detail(self, lang, dive): - self.global_sync.sync_metas(lang, diving_views.DiveMeta, dive) - self.global_sync.sync_pdf(lang, dive, diving_views.DiveDocumentPublic.as_view(model=type(dive))) - if 'geotrek.trekking' in settings.INSTALLED_APPS: - self.sync_pois(lang, dive) - self.sync_services(lang, dive) - for picture, resized in dive.resized_pictures: - self.global_sync.sync_media_file(lang, resized) - for poi in dive.published_pois: - if poi.resized_pictures: - self.global_sync.sync_media_file(lang, poi.resized_pictures[0][1]) - for picture, resized in poi.resized_pictures[1:]: - self.global_sync.sync_media_file(lang, resized) - for other_file in poi.files: - self.global_sync.sync_media_file(lang, other_file.attachment_file) - if 'geotrek.tourism' in settings.INSTALLED_APPS: - if self.global_sync.with_events: - self.sync_touristicevents(lang, dive) - if self.global_sync.categories: - self.sync_touristiccontents(lang, dive) - if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - self.sync_sensitiveareas(lang, dive) - - def sync_touristiccontents(self, lang, dive): - params = {'format': 'geojson', - 'categories': ','.join(category for category in self.global_sync.categories)} - self.global_sync.get_params_portal(params) - view = tourism_views.DiveTouristicContentViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'dives', str(dive.pk), 'touristiccontents.geojson') - self.global_sync.sync_view(lang, view, name, params=params, pk=dive.pk) - - for content in dive.touristic_contents.all(): - self.sync_touristiccontent_media(lang, content) - - def sync_touristicevents(self, lang, dive): - params = {'format': 'geojson'} - self.global_sync.get_params_portal(params) - view = tourism_views.DiveTouristicEventViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'dives', str(dive.pk), 'touristicevents.geojson') - self.global_sync.sync_view(lang, view, name, params=params, pk=dive.pk) - - for event in dive.touristic_events.all(): - self.sync_touristicevent_media(lang, event) - - def sync_sensitiveareas(self, lang, dive): - params = {'format': 'geojson', 'practices': 'Terrestre'} - - view = sensitivity_views.DiveSensitiveAreaViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'dives', str(dive.pk), 'sensitiveareas.geojson') - self.global_sync.sync_view(lang, view, name, params=params, pk=dive.pk) - - def sync_touristicevent_media(self, lang, event, zipfile=None): - if event.resized_pictures: - self.global_sync.sync_media_file(lang, event.resized_pictures[0][1], zipfile=zipfile) - for picture, resized in event.resized_pictures[1:]: - self.global_sync.sync_media_file(lang, resized) - - def sync_touristiccontent_media(self, lang, content, zipfile=None): - if content.resized_pictures: - self.global_sync.sync_media_file(lang, content.resized_pictures[0][1], zipfile=zipfile) - for picture, resized in content.resized_pictures[1:]: - self.global_sync.sync_media_file(lang, resized) diff --git a/geotrek/diving/locale/de/LC_MESSAGES/django.po b/geotrek/diving/locale/de/LC_MESSAGES/django.po index c1ebb685c3..0cdc16dbdf 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -130,10 +130,6 @@ msgstr "" msgid "Dives" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "dive" -msgstr "" - msgid "Add a new dive" msgstr "" diff --git a/geotrek/diving/locale/en/LC_MESSAGES/django.po b/geotrek/diving/locale/en/LC_MESSAGES/django.po index c1ebb685c3..0cdc16dbdf 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -130,10 +130,6 @@ msgstr "" msgid "Dives" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "dive" -msgstr "" - msgid "Add a new dive" msgstr "" diff --git a/geotrek/diving/locale/es/LC_MESSAGES/django.po b/geotrek/diving/locale/es/LC_MESSAGES/django.po index c1ebb685c3..0cdc16dbdf 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -130,10 +130,6 @@ msgstr "" msgid "Dives" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "dive" -msgstr "" - msgid "Add a new dive" msgstr "" diff --git a/geotrek/diving/locale/fr/LC_MESSAGES/django.po b/geotrek/diving/locale/fr/LC_MESSAGES/django.po index 8a33a9d23d..6970618545 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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" @@ -130,10 +130,6 @@ msgstr "" msgid "Dives" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "dive" -msgstr "" - msgid "Add a new dive" msgstr "" diff --git a/geotrek/diving/locale/nl/LC_MESSAGES/django.po b/geotrek/diving/locale/nl/LC_MESSAGES/django.po index c1ebb685c3..0cdc16dbdf 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -130,10 +130,6 @@ msgstr "" msgid "Dives" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "dive" -msgstr "" - msgid "Add a new dive" msgstr "" diff --git a/geotrek/diving/models.py b/geotrek/diving/models.py index 77633031f3..05e1da847b 100644 --- a/geotrek/diving/models.py +++ b/geotrek/diving/models.py @@ -1,6 +1,5 @@ from django.conf import settings from django.contrib.gis.db import models -from django.template.defaultfilters import slugify from django.urls import reverse from django.utils.translation import get_language, gettext_lazy as _ @@ -35,14 +34,6 @@ class Meta: def __str__(self): return self.name - @property - def prefixed_id(self): - return '{prefix}{id}'.format(prefix=self.id_prefix, id=self.id) - - @property - def slug(self): - return slugify(self.name) or str(self.pk) - class Difficulty(OptionalPictogramMixin): """We use an IntegerField for id, since we want to edit it in Admin. @@ -135,14 +126,6 @@ class Meta: def __str__(self): return self.name - @property - def rando_url(self): - if settings.SPLIT_DIVES_CATEGORIES_BY_PRACTICE and self.practice: - category_slug = self.practice.slug - else: - category_slug = _('dive') - return '{}/{}/'.format(category_slug, self.slug) - @property def display_geom(self): return "{} ({})".format(format_coordinates(self.geom), spatial_reference()) @@ -150,13 +133,6 @@ def display_geom(self): def distance(self, to_cls): return settings.DIVING_INTERSECTION_MARGIN - @property - def prefixed_category_id(self): - if settings.SPLIT_DIVES_CATEGORIES_BY_PRACTICE and self.practice: - return self.practice.prefixed_id - else: - return Practice.id_prefix - def get_map_image_url(self): return reverse('diving:dive_map_image', args=[str(self.pk), get_language()]) diff --git a/geotrek/diving/serializers.py b/geotrek/diving/serializers.py index c630193e45..29eb9639f2 100644 --- a/geotrek/diving/serializers.py +++ b/geotrek/diving/serializers.py @@ -1,50 +1,9 @@ -from django.conf import settings -from django.utils.translation import gettext_lazy as _ from drf_dynamic_fields import DynamicFieldsMixin from mapentity.serializers import MapentityGeojsonModelSerializer from rest_framework import serializers as rest_serializers -from rest_framework_gis import fields as rest_gis_fields -from rest_framework_gis.serializers import GeoFeatureModelSerializer -from geotrek.common.serializers import (ThemeSerializer, PublishableSerializerMixin, - PictogramSerializerMixin, RecordSourceSerializer, - PicturesSerializerMixin, TranslatedModelSerializer, - TargetPortalSerializer) from geotrek.diving import models as diving_models -from geotrek.trekking import serializers as trekking_serializers - - -class DifficultySerializer(PictogramSerializerMixin, TranslatedModelSerializer): - label = rest_serializers.ReadOnlyField(source='name') - - class Meta: - model = diving_models.Difficulty - fields = ('id', 'pictogram', 'label') - - -class LevelSerializer(PictogramSerializerMixin, TranslatedModelSerializer): - label = rest_serializers.ReadOnlyField(source='name') - - class Meta: - model = diving_models.Level - fields = ('id', 'pictogram', 'label', 'description') - - -class PracticeSerializer(PictogramSerializerMixin, TranslatedModelSerializer): - label = rest_serializers.ReadOnlyField(source='name') - - class Meta: - model = diving_models.Practice - fields = ('id', 'pictogram', 'label') - - -class CloseDiveSerializer(TranslatedModelSerializer): - category_id = rest_serializers.ReadOnlyField(source='prefixed_category_id') - - class Meta: - model = diving_models.Dive - fields = ('id', 'category_id') class DiveSerializer(DynamicFieldsMixin, rest_serializers.ModelSerializer): @@ -63,67 +22,3 @@ class DiveGeojsonSerializer(MapentityGeojsonModelSerializer): class Meta(MapentityGeojsonModelSerializer.Meta): model = diving_models.Dive fields = ['id', 'name', 'published'] - - -class DiveAPISerializer(PicturesSerializerMixin, PublishableSerializerMixin, TranslatedModelSerializer): - themes = ThemeSerializer(many=True) - practice = PracticeSerializer() - difficulty = DifficultySerializer() - levels = LevelSerializer(many=True) - source = RecordSourceSerializer(many=True) - portal = TargetPortalSerializer(many=True) - category = rest_serializers.SerializerMethodField() - dives = CloseDiveSerializer(many=True, source='published_dives') - treks = trekking_serializers.CloseTrekSerializer(many=True, source='published_treks') - pois = trekking_serializers.ClosePOISerializer(many=True, source='published_pois') - - def __init__(self, instance=None, *args, **kwargs): - super().__init__(instance, *args, **kwargs) - if 'geotrek.tourism' in settings.INSTALLED_APPS: - - from geotrek.tourism import serializers as tourism_serializers - - self.fields['touristic_contents'] = tourism_serializers.CloseTouristicContentSerializer(many=True, - source='published_touristic_contents') - self.fields['touristic_events'] = tourism_serializers.CloseTouristicEventSerializer(many=True, - source='published_touristic_events') - - class Meta: - model = diving_models.Dive - fields = ( - 'id', 'practice', 'description_teaser', 'description', 'advice', - 'difficulty', 'levels', 'themes', 'owner', 'depth', - 'facilities', 'departure', 'disabled_sport', 'category', - 'source', 'portal', 'eid', 'dives', 'treks', 'pois' - ) + PublishableSerializerMixin.Meta.fields + PicturesSerializerMixin.Meta.fields - - def get_category(self, obj): - if settings.SPLIT_DIVES_CATEGORIES_BY_PRACTICE and obj.practice: - data = { - 'id': obj.prefixed_category_id, - 'label': obj.practice.name, - 'pictogram': obj.practice.get_pictogram_url(), - 'slug': obj.practice.slug, - } - else: - data = { - 'id': obj.prefixed_category_id, - 'label': _("Dive"), - 'pictogram': '/static/diving/dive.svg', - # Translators: This is a slug (without space, accent or special char) - 'slug': _('dive'), - } - if settings.SPLIT_DIVES_CATEGORIES_BY_PRACTICE: - data['order'] = obj.practice and obj.practice.order - else: - data['order'] = settings.DIVE_CATEGORY_ORDER - return data - - -class DiveAPIGeojsonSerializer(GeoFeatureModelSerializer, DiveAPISerializer): - # Annotated geom field with API_SRID - api_geom = rest_gis_fields.GeometryField(read_only=True, precision=7) - - class Meta(DiveAPISerializer.Meta): - geo_field = 'api_geom' - fields = DiveAPISerializer.Meta.fields + ('api_geom', ) diff --git a/geotrek/diving/templates/diving/dive_meta.html b/geotrek/diving/templates/diving/dive_meta.html deleted file mode 100644 index 554be0e922..0000000000 --- a/geotrek/diving/templates/diving/dive_meta.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %}{% get_current_language as LANGUAGE_CODE %} - - - - - {{ object.name }} - - - - - - {% if object.resized_pictures %} - - - - {% else %} - - - - {% endif %} - - - -

{{ object.name }}

-{{ META_TITLE }} - - diff --git a/geotrek/diving/tests/test_helpers_sync.py b/geotrek/diving/tests/test_helpers_sync.py deleted file mode 100644 index 28bcf768a9..0000000000 --- a/geotrek/diving/tests/test_helpers_sync.py +++ /dev/null @@ -1,100 +0,0 @@ -import os -from unittest.mock import patch -from io import StringIO - -from django.test import TestCase -from django.conf import settings - -from geotrek.common.tests.factories import FakeSyncCommand, RecordSourceFactory, TargetPortalFactory, AttachmentFactory -from geotrek.common.utils.testdata import get_dummy_uploaded_image, get_dummy_uploaded_file -from geotrek.diving.tests.factories import DiveFactory, PracticeFactory -from geotrek.trekking.tests.factories import POIFactory -from geotrek.tourism.tests.factories import TouristicContentFactory, TouristicEventFactory - -from geotrek.diving.helpers_sync import SyncRando - - -@patch('geotrek.diving.models.Dive.prepare_map_image') -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.practice_dive = PracticeFactory.create(order=0) - cls.dive = DiveFactory.create(practice=cls.practice_dive, published=True, - geom='SRID=2154;POINT(700001 6600001)') - cls.attachment_dive = AttachmentFactory.create(content_object=cls.dive, - attachment_file=get_dummy_uploaded_image()) - cls.poi_dive = POIFactory.create(name="dive_poi", published=True) - AttachmentFactory.create(content_object=cls.poi_dive, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.poi_dive, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.poi_dive, - attachment_file=get_dummy_uploaded_file()) - cls.touristic_content = TouristicContentFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.touristic_event = TouristicEventFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.attachment_touristic_content = AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - cls.attachment_touristic_event = AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - cls.source_a = RecordSourceFactory() - cls.source_b = RecordSourceFactory() - - cls.portal_a = TargetPortalFactory() - cls.portal_b = TargetPortalFactory() - cls.dive_portal_source = DiveFactory.create(practice=cls.practice_dive, published=True, - geom='SRID=2154;POINT(700002 6600002)', - portals=(cls.portal_a,), sources=(cls.source_a,)) - cls.dive_other_portal_source = DiveFactory.create(practice=cls.practice_dive, published=True, - geom='SRID=2154;POINT(700002 6600002)', - portals=(cls.portal_b,), sources=(cls.source_b,)) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_detail_no_portal_no_source(self, stdout, mock_prepare): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync_detail('fr', self.dive) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'dives', - str(self.dive.pk), '%s.pdf' % self.dive.slug))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_detail_portal_source(self, stdout, mock_prepare): - command = FakeSyncCommand(portal=self.portal_b.name, source=[self.source_b.name]) - synchro = SyncRando(command) - synchro.sync_detail('fr', self.dive) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'dives', - str(self.dive.pk), '%s.pdf' % self.dive.slug))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'dives', - str(self.dive.pk), 'pois.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'dives', - str(self.dive.pk), 'touristiccontents.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'dives', - str(self.dive.pk), 'touristicevents.geojson'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_language(self, stdout, mock_prepare): - def side_effect_sync(lang, trek): - pass - command = FakeSyncCommand() - synchro = SyncRando(command) - with patch('geotrek.diving.helpers_sync.SyncRando.sync_detail', side_effect=side_effect_sync) as mock_dive: - synchro.sync('en') - self.assertEqual(len(mock_dive.call_args_list), 3) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'dives.geojson'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_language_portal_source(self, stdout, mock_prepare): - def side_effect_sync(lang, dive): - self.assertEqual(dive, self.dive_portal_source) - command = FakeSyncCommand(portal=self.portal_a.name, source=[self.source_a.name]) - synchro = SyncRando(command) - with patch('geotrek.diving.helpers_sync.SyncRando.sync_detail', side_effect=side_effect_sync) as mock_dive: - synchro.sync('en') - self.assertEqual(len(mock_dive.call_args_list), 1) - mock_dive.assert_called_with('en', self.dive_portal_source) diff --git a/geotrek/diving/tests/test_models.py b/geotrek/diving/tests/test_models.py index f8cf17c520..e53a480c95 100644 --- a/geotrek/diving/tests/test_models.py +++ b/geotrek/diving/tests/test_models.py @@ -1,9 +1,8 @@ from geotrek.common.tests import TranslationResetMixin from geotrek.diving.tests.factories import DiveFactory, LevelFactory -from geotrek.diving.tests.factories import PracticeFactory -from django.test import TestCase, override_settings +from django.test import TestCase class DiveModelTest(TranslationResetMixin, TestCase): @@ -19,33 +18,3 @@ def test_levels_display(self): d = DiveFactory() d.levels.set([l1, l2]) self.assertEquals(d.levels_display, "{0}, {1}".format(l1, l2)) - - def test_rando_url(self): - self.assertEqual(self.dive.rando_url, "dive/dive/") - - @override_settings(SPLIT_DIVES_CATEGORIES_BY_PRACTICE=True) - def test_rando_url_split_by_category_no_practice(self): - self.assertEqual(self.dive.rando_url, "dive/dive/") - - @override_settings(SPLIT_DIVES_CATEGORIES_BY_PRACTICE=True) - def test_rando_url_split_by_category(self): - practice = PracticeFactory.create(name="special") - dive = DiveFactory.create() - dive.practice = practice - dive.save() - self.assertEqual(dive.rando_url, "special/dive/") - - def test_prefixed_category_id(self): - self.assertEqual(self.dive.prefixed_category_id, "D") - - @override_settings(SPLIT_DIVES_CATEGORIES_BY_PRACTICE=True) - def test_prefixed_category_id_no_practice(self): - self.assertEqual(self.dive.prefixed_category_id, "D") - - @override_settings(SPLIT_DIVES_CATEGORIES_BY_PRACTICE=True) - def test_prefixed_category_id_with_practice(self): - practice = PracticeFactory.create(name="special") - dive = DiveFactory.create() - dive.practice = practice - dive.save() - self.assertEqual(dive.prefixed_category_id, "D%s" % practice.id) diff --git a/geotrek/diving/tests/test_views.py b/geotrek/diving/tests/test_views.py index 883eed30e1..d5716c93dc 100644 --- a/geotrek/diving/tests/test_views.py +++ b/geotrek/diving/tests/test_views.py @@ -1,16 +1,19 @@ -from django.urls import reverse -from django.utils import translation from django.utils.translation import gettext_lazy as _ +from mapentity.tests.factories import SuperUserFactory from geotrek.authent.tests.factories import StructureFactory -from geotrek.common.tests import CommonLiveTest, CommonTest, GeotrekAPITestCase -from geotrek.diving.models import Dive, Level -from geotrek.diving.tests.factories import DiveWithLevelsFactory, DiveFactory, DivingManagerFactory, PracticeFactory +from geotrek.common.tests import CommonLiveTest, CommonTest -from mapentity.tests.factories import SuperUserFactory +from ..models import Dive +from .factories import ( + DiveFactory, + DiveWithLevelsFactory, + DivingManagerFactory, + PracticeFactory, +) -class DiveViewsTests(GeotrekAPITestCase, CommonTest): +class DiveViewsTests(CommonTest): model = Dive modelfactory = DiveWithLevelsFactory userfactory = DivingManagerFactory @@ -29,64 +32,7 @@ def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, 'name': self.obj.name, - 'draft': self.obj.draft - } - - def get_expected_json_attrs(self): - return { - 'advice': '', - 'category': { - 'id': 'D{}'.format(self.obj.practice.id), - 'label': 'Practice', - 'order': None, - 'pictogram': '/media/upload/dummy_img.png', - 'slug': self.obj.practice.slug, - }, - 'departure': '', - 'depth': None, - 'description': '', - 'description_teaser': '', - 'difficulty': None, - 'disabled_sport': '', - 'dives': [], - 'eid': None, - 'facilities': '', - 'filelist_url': '/paperclip/get/diving/dive/{}/'.format(self.obj.pk), - 'files': [], - 'levels': [ - {'description': '

Description

', - 'id': Level.objects.first().pk, - 'label': 'Level', - 'pictogram': '/media/upload/level.png'} - ], - 'map_image_url': '/image/dive-{}-en.png'.format(self.obj.pk), - 'name': 'Dive', - 'owner': '', - 'pictures': [], - 'pois': [], - 'portal': [], - 'practice': { - 'id': self.obj.practice.pk, - 'label': 'Practice', - 'pictogram': '/media/upload/dummy_img.png' - }, - 'printable': '/api/en/dives/{}/dive.pdf'.format(self.obj.pk), - 'publication_date': '2020-03-17', 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False} - ], - 'slug': self.obj.slug, - 'source': [], - 'themes': [], - 'thumbnail': None, - 'touristic_contents': [], - 'touristic_events': [], - 'treks': [], - 'videos': [], } def get_expected_datatables_attrs(self): @@ -110,26 +56,6 @@ def get_good_data(self): 'geom': '{"type": "Point", "coordinates":[0, 0]}', } - def test_services_on_treks_do_not_exist(self): - self.modelfactory.create() - response = self.client.get(reverse('diving:dive_service_geojson', kwargs={'lang': translation.get_language(), 'pk': 0})) - self.assertEqual(response.status_code, 404) - - def test_services_on_treks_not_public(self): - dive = self.modelfactory.create(published=False) - response = self.client.get(reverse('diving:dive_service_geojson', kwargs={'lang': translation.get_language(), 'pk': dive.pk})) - self.assertEqual(response.status_code, 404) - - def test_pois_on_treks_do_not_exist(self): - self.modelfactory.create() - response = self.client.get(reverse('diving:dive_poi_geojson', kwargs={'lang': translation.get_language(), 'pk': 0})) - self.assertEqual(response.status_code, 404) - - def test_pois_on_treks_not_public(self): - dive = self.modelfactory.create(published=False) - response = self.client.get(reverse('diving:dive_poi_geojson', kwargs={'lang': translation.get_language(), 'pk': dive.pk})) - self.assertEqual(response.status_code, 404) - class DiveViewsLiveTests(CommonLiveTest): model = Dive diff --git a/geotrek/diving/urls.py b/geotrek/diving/urls.py index f495df6009..bd62ca8104 100644 --- a/geotrek/diving/urls.py +++ b/geotrek/diving/urls.py @@ -2,23 +2,18 @@ from django.urls import path, register_converter from mapentity.registry import registry -from rest_framework.routers import DefaultRouter from geotrek.common.urls import PublishableEntityOptions, LangConverter from . import models -from .views import (DiveMapImage, DivePOIViewSet, DiveServiceViewSet, DiveAPIViewSet, - DiveDocumentBookletPublic, DiveDocumentPublic, DiveMarkupPublic) +from .views import DiveMapImage, DiveDocumentBookletPublic, DiveDocumentPublic, DiveMarkupPublic register_converter(LangConverter, 'lang') app_name = 'diving' + urlpatterns = [ path('image/dive--.png', DiveMapImage.as_view(), name='dive_map_image'), - path('api//dives//pois.geojson', DivePOIViewSet.as_view({'get': 'list'}), - name="dive_poi_geojson"), - path('api//dives//services.geojson', DiveServiceViewSet.as_view({'get': 'list'}), - name="dive_service_geojson"), ] @@ -27,14 +22,5 @@ class DiveEntityOptions(PublishableEntityOptions): document_public_view = DiveDocumentPublic markup_public_view = DiveMarkupPublic - def get_queryset(self): - return self.model.objects.existing() - - -router = DefaultRouter(trailing_slash=False) - - -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/dives', DiveAPIViewSet, basename='dive') -urlpatterns += router.urls urlpatterns += registry.register(models.Dive, DiveEntityOptions, menu=settings.DIVE_MODEL_ENABLED) diff --git a/geotrek/diving/views.py b/geotrek/diving/views.py index c6a2dd7387..2e68e9918f 100644 --- a/geotrek/diving/views.py +++ b/geotrek/diving/views.py @@ -1,27 +1,18 @@ from django.conf import settings from django.contrib.gis.db.models.functions import Transform -from django.db.models import Q -from django.http import Http404 -from django.shortcuts import get_object_or_404 from django.utils import translation -from django.views.generic import DetailView from mapentity.views import (MapEntityList, MapEntityFormat, MapEntityDetail, MapEntityMapImage, MapEntityDocument, MapEntityCreate, MapEntityUpdate, MapEntityDelete) -from rest_framework import permissions as rest_permissions, viewsets from geotrek.authent.decorators import same_structure_required -from geotrek.common.mixins.api import APIViewSet -from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin, MetaMixin -from geotrek.common.models import RecordSource, TargetPortal +from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin from geotrek.common.views import DocumentPublic, DocumentBookletPublic, MarkupPublic from geotrek.common.viewsets import GeotrekMapentityViewSet -from geotrek.trekking.models import POI, Service -from geotrek.trekking.serializers import POIAPIGeojsonSerializer, ServiceAPIGeojsonSerializer from geotrek.trekking.views import FlattenPicturesMixin from .filters import DiveFilterSet from .forms import DiveForm from .models import Dive -from .serializers import DiveSerializer, DiveGeojsonSerializer, DiveAPIGeojsonSerializer, DiveAPISerializer +from .serializers import DiveSerializer, DiveGeojsonSerializer class DiveList(CustomColumnsMixin, FlattenPicturesMixin, MapEntityList): @@ -82,22 +73,8 @@ class DiveDocumentPublicMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dive = self.get_object() - context['headerimage_ratio'] = settings.EXPORT_HEADER_IMAGE_SIZE['dive'] - context['object'] = context['dive'] = dive - source = self.request.GET.get('source') - if source: - try: - context['source'] = RecordSource.objects.get(name=source) - except RecordSource.DoesNotExist: - pass - portal = self.request.GET.get('portal') - if portal: - try: - context['portal'] = TargetPortal.objects.get(name=portal) - except TargetPortal.DoesNotExist: - pass return context @@ -135,11 +112,6 @@ def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) -class DiveMeta(MetaMixin, DetailView): - model = Dive - template_name = 'diving/dive_meta.html' - - class DiveViewSet(GeotrekMapentityViewSet): model = Dive serializer_class = DiveSerializer @@ -154,53 +126,3 @@ def get_queryset(self): qs = qs.only('id', 'name', 'published') return qs - - -class DiveAPIViewSet(APIViewSet): - model = Dive - serializer_class = DiveAPISerializer - geojson_serializer_class = DiveAPIGeojsonSerializer - - def get_queryset(self): - qs = self.model.objects.existing() - qs = qs.select_related('structure', 'difficulty', 'practice') - qs = qs.prefetch_related('levels', 'source', 'portal', 'themes', 'attachments') - qs = qs.filter(published=True).order_by('pk').distinct('pk') - if 'source' in self.request.GET: - qs = qs.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - qs = qs.filter(Q(portal__name=self.request.GET['portal']) | Q(portal=None)) - - qs = qs.annotate(api_geom=Transform("geom", settings.API_SRID)) - - return qs - - -class DivePOIViewSet(viewsets.ModelViewSet): - model = POI - serializer_class = POIAPIGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - pk = self.kwargs['pk'] - dive = get_object_or_404(Dive.objects.existing(), pk=pk) - if not dive.is_public(): - raise Http404 - return dive.pois.filter(published=True).annotate(api_geom=Transform("geom", settings.API_SRID)) - - -class DiveServiceViewSet(viewsets.ModelViewSet): - model = Service - serializer_class = ServiceAPIGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - pk = self.kwargs['pk'] - dive = get_object_or_404(Dive.objects.existing(), pk=pk) - if not dive.is_public(): - raise Http404 - return dive.services.filter(type__published=True).annotate(api_geom=Transform("geom", settings.API_SRID)) - -# Translations for public PDF -# translation.gettext_noop("...") diff --git a/geotrek/feedback/helpers.py b/geotrek/feedback/helpers.py index d149872283..5e20d0e2d5 100644 --- a/geotrek/feedback/helpers.py +++ b/geotrek/feedback/helpers.py @@ -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): diff --git a/geotrek/feedback/helpers_sync.py b/geotrek/feedback/helpers_sync.py deleted file mode 100644 index 469b9c97a9..0000000000 --- a/geotrek/feedback/helpers_sync.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -from geotrek.feedback import views - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_view(lang, views.CategoryList.as_view(), - os.path.join('api', lang, 'feedback', 'categories.json'), - zipfile=self.global_sync.zipfile) - self.global_sync.sync_view(lang, views.FeedbackOptionsView.as_view(), - os.path.join('api', lang, 'feedback', 'options.json'), - zipfile=self.global_sync.zipfile) diff --git a/geotrek/feedback/locale/de/LC_MESSAGES/django.po b/geotrek/feedback/locale/de/LC_MESSAGES/django.po index 4231e6ee96..ee439286d8 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -313,21 +313,3 @@ msgstr "" msgid "Data to import from Suricate" msgstr "" - -msgid "Invalid geometry value." -msgstr "" - -msgid "Geotrek : Signal a mistake" -msgstr "" - -msgid "" -"Hello,\n" -"\n" -"We acknowledge receipt of your feedback, thank you for your interest in " -"Geotrek.\n" -"\n" -"Best regards,\n" -"\n" -"The Geotrek Team\n" -"http://www.geotrek.fr" -msgstr "" diff --git a/geotrek/feedback/locale/en/LC_MESSAGES/django.po b/geotrek/feedback/locale/en/LC_MESSAGES/django.po index 4231e6ee96..ee439286d8 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -313,21 +313,3 @@ msgstr "" msgid "Data to import from Suricate" msgstr "" - -msgid "Invalid geometry value." -msgstr "" - -msgid "Geotrek : Signal a mistake" -msgstr "" - -msgid "" -"Hello,\n" -"\n" -"We acknowledge receipt of your feedback, thank you for your interest in " -"Geotrek.\n" -"\n" -"Best regards,\n" -"\n" -"The Geotrek Team\n" -"http://www.geotrek.fr" -msgstr "" diff --git a/geotrek/feedback/locale/es/LC_MESSAGES/django.po b/geotrek/feedback/locale/es/LC_MESSAGES/django.po index 4231e6ee96..ee439286d8 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -313,21 +313,3 @@ msgstr "" msgid "Data to import from Suricate" msgstr "" - -msgid "Invalid geometry value." -msgstr "" - -msgid "Geotrek : Signal a mistake" -msgstr "" - -msgid "" -"Hello,\n" -"\n" -"We acknowledge receipt of your feedback, thank you for your interest in " -"Geotrek.\n" -"\n" -"Best regards,\n" -"\n" -"The Geotrek Team\n" -"http://www.geotrek.fr" -msgstr "" diff --git a/geotrek/feedback/locale/fr/LC_MESSAGES/django.po b/geotrek/feedback/locale/fr/LC_MESSAGES/django.po index e28a879deb..c8f5fc453a 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -327,30 +327,3 @@ msgstr "Consultez vos signalements sur" msgid "Data to import from Suricate" msgstr "Données à importer depuis Suricate" - -msgid "Invalid geometry value." -msgstr "Géométrie invalide" - -msgid "Geotrek : Signal a mistake" -msgstr "Geotrek : signalement d'un problème" - -msgid "" -"Hello,\n" -"\n" -"We acknowledge receipt of your feedback, thank you for your interest in " -"Geotrek.\n" -"\n" -"Best regards,\n" -"\n" -"The Geotrek Team\n" -"http://www.geotrek.fr" -msgstr "" -"Bonjour,\n" -"\n" -"Nous accusons réception de votre signalement, merci de votre intérêt pour " -"Geotrek.\n" -"\n" -"Cordialement,\n" -"\n" -"L'équipe Geotrek\n" -"http://www.geotrek.fr" diff --git a/geotrek/feedback/locale/it/LC_MESSAGES/django.po b/geotrek/feedback/locale/it/LC_MESSAGES/django.po index 4231e6ee96..ee439286d8 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -313,21 +313,3 @@ msgstr "" msgid "Data to import from Suricate" msgstr "" - -msgid "Invalid geometry value." -msgstr "" - -msgid "Geotrek : Signal a mistake" -msgstr "" - -msgid "" -"Hello,\n" -"\n" -"We acknowledge receipt of your feedback, thank you for your interest in " -"Geotrek.\n" -"\n" -"Best regards,\n" -"\n" -"The Geotrek Team\n" -"http://www.geotrek.fr" -msgstr "" diff --git a/geotrek/feedback/locale/nl/LC_MESSAGES/django.po b/geotrek/feedback/locale/nl/LC_MESSAGES/django.po index 4231e6ee96..ee439286d8 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -313,21 +313,3 @@ msgstr "" msgid "Data to import from Suricate" msgstr "" - -msgid "Invalid geometry value." -msgstr "" - -msgid "Geotrek : Signal a mistake" -msgstr "" - -msgid "" -"Hello,\n" -"\n" -"We acknowledge receipt of your feedback, thank you for your interest in " -"Geotrek.\n" -"\n" -"Best regards,\n" -"\n" -"The Geotrek Team\n" -"http://www.geotrek.fr" -msgstr "" diff --git a/geotrek/feedback/management/commands/sync_suricate.py b/geotrek/feedback/management/commands/sync_suricate.py index 0db96b5bae..ec556e100e 100644 --- a/geotrek/feedback/management/commands/sync_suricate.py +++ b/geotrek/feedback/management/commands/sync_suricate.py @@ -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__) @@ -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: @@ -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() diff --git a/geotrek/feedback/serializers.py b/geotrek/feedback/serializers.py index f0c65e28dc..03534926ee 100644 --- a/geotrek/feedback/serializers.py +++ b/geotrek/feedback/serializers.py @@ -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 @@ -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 diff --git a/geotrek/feedback/tests/test_helpers_sync.py b/geotrek/feedback/tests/test_helpers_sync.py deleted file mode 100644 index ea0eeecf0b..0000000000 --- a/geotrek/feedback/tests/test_helpers_sync.py +++ /dev/null @@ -1,25 +0,0 @@ -from io import StringIO -import os -from unittest.mock import patch - -from django.conf import settings -from django.test import TestCase - -from geotrek.common.tests.factories import FakeSyncCommand -from geotrek.feedback.tests.factories import ReportCategoryFactory -from geotrek.feedback.helpers_sync import SyncRando - - -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.report_category = ReportCategoryFactory.create() - - @patch('sys.stdout', new_callable=StringIO) - def test_feedback(self, stdout): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'feedback', - 'categories.json'))) diff --git a/geotrek/feedback/tests/test_views.py b/geotrek/feedback/tests/test_views.py index a691736298..d3de2b6419 100644 --- a/geotrek/feedback/tests/test_views.py +++ b/geotrek/feedback/tests/test_views.py @@ -1,6 +1,5 @@ import csv import json -from datetime import datetime from io import StringIO from unittest import mock @@ -12,28 +11,25 @@ from django.test.utils import override_settings from django.utils import translation from django.utils.module_loading import import_string -from django.utils.translation import gettext_lazy as _ from freezegun import freeze_time from mapentity.tests.factories import SuperUserFactory, UserFactory -from paperclip.models import random_suffix_regexp from rest_framework.reverse import reverse -from rest_framework.test import APIClient from geotrek.authent.tests.base import AuthentFixturesMixin -from geotrek.common.tests import (CommonTest, GeotrekAPITestCase, - TranslationResetMixin) -from geotrek.common.utils.testdata import (get_dummy_uploaded_document, - get_dummy_uploaded_image, - get_dummy_uploaded_image_svg) from geotrek.feedback import models as feedback_models from geotrek.maintenance.tests.factories import ( - InfrastructureInterventionFactory, ReportInterventionFactory) + InfrastructureInterventionFactory, + ReportInterventionFactory, +) from . import factories as feedback_factories -from .test_suricate_sync import (SURICATE_REPORT_SETTINGS, - test_for_all_suricate_modes, - test_for_report_and_basic_modes, - test_for_workflow_mode) +from .test_suricate_sync import ( + SURICATE_REPORT_SETTINGS, + test_for_all_suricate_modes, + test_for_report_and_basic_modes, + test_for_workflow_mode, +) +from ...common.tests import CommonTest class ReportViewsetMailSend(TestCase): @@ -46,7 +42,7 @@ def test_mail_send_on_request(self, mocked_post): mock_response.content = json.dumps({"code_ok": 'true'}).encode() mock_response.status_code = 200 mocked_post.return_value = mock_response - self.client.post( + response = self.client.post( '/api/en/reports/report', { 'geom': '{\"type\":\"Point\",\"coordinates\":[4.3728446995373815,43.856935212211454]}', @@ -55,10 +51,10 @@ def test_mail_send_on_request(self, mocked_post): 'activity': feedback_factories.ReportActivityFactory.create().pk, 'problem_magnitude': feedback_factories.ReportProblemMagnitudeFactory.create().pk, }) - self.assertEqual(len(mail.outbox), 2) - self.assertEqual(mail.outbox[1].subject, "Geotrek : Signal a mistake") - self.assertIn("We acknowledge receipt of your feedback", mail.outbox[1].body) - self.assertEqual(mail.outbox[1].from_email, settings.DEFAULT_FROM_EMAIL) + self.assertEqual(response.status_code, 201) + self.assertEqual(mail.outbox[-1].subject, "Geotrek : Signal a mistake") + self.assertIn("We acknowledge receipt of your feedback", mail.outbox[-1].body) + self.assertEqual(mail.outbox[-1].from_email, settings.DEFAULT_FROM_EMAIL) created_report = feedback_models.Report.objects.filter(email="test_post@geotrek.local").first() self.assertEqual(created_report.comment, "Test comment <>") @@ -114,7 +110,7 @@ def test_report_layer_cache(self): self.filed_report = feedback_factories.ReportFactory(status=self.filed_status) -class ReportViewsTest(GeotrekAPITestCase, CommonTest): +class ReportViewsTest(CommonTest): model = feedback_models.Report modelfactory = feedback_factories.ReportFactory userfactory = SuperUserFactory @@ -135,18 +131,8 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'activity': self.obj.activity.pk, - 'category': self.obj.category.pk, - 'comment': self.obj.comment, - 'related_trek': None, - 'email': self.obj.email, - 'status': self.obj.status_id, - 'problem_magnitude': self.obj.problem_magnitude.pk + 'color': self.obj.status.color, + 'name': str(self.obj), } def get_expected_datatables_attrs(self): @@ -160,7 +146,7 @@ def get_expected_datatables_attrs(self): } def get_bad_data(self): - return {'geom': 'FOO'}, _('Invalid geometry value.') + return {'geom': 'FOO'}, 'Invalid geometry value.' def get_good_data(self): return { @@ -261,132 +247,6 @@ def test_api_datatables_list_for_model_in_suricate_mode(self): 'recordsTotal': 1}) -class BaseAPITest(TestCase): - @classmethod - def setUpTestData(cls): - cls.user = UserFactory(password='booh') - perm = Permission.objects.get_by_natural_key('add_report', 'feedback', 'report') - cls.user.user_permissions.add(perm) - - cls.login_url = '/login/' - - -class CreateReportsAPITest(BaseAPITest): - @classmethod - def setUpTestData(cls): - cls.add_url = '/api/en/reports/report' - cls.data = { - 'geom': '{"type": "Point", "coordinates": [3, 46.5]}', - 'email': 'yeah@you.com', - '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='yeah@you.com').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='yeah@you.com').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='yeah@you.com').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.feedback.views.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='yeah@you.com').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 ") - self.assertEqual(report.attachments.count(), 0) - - @mock.patch('geotrek.feedback.views.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='yeah@you.com').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) - - -class ListCategoriesTest(TranslationResetMixin, BaseAPITest): - @classmethod - def setUpTestData(cls): - cls.cat = feedback_factories.ReportCategoryFactory(label_it='Obstaculo') - - def test_categories_can_be_obtained_as_json(self): - response = self.client.get('/api/en/feedback/categories.json') - self.assertEqual(response.status_code, 200) - data = response.json() - self.assertEqual(data[0]['id'], self.cat.id) - self.assertEqual(data[0]['label'], self.cat.label) - - def test_categories_are_translated(self): - response = self.client.get('/api/it/feedback/categories.json') - self.assertEqual(response.status_code, 200) - data = response.json() - self.assertEqual(data[0]['label'], self.cat.label_it) - - -class ListOptionsTest(TranslationResetMixin, BaseAPITest): - @classmethod - def setUpTestData(cls): - cls.activity = feedback_factories.ReportActivityFactory(label_it='Hiking') - cls.cat = feedback_factories.ReportCategoryFactory(label_it='Obstaculo') - cls.pb_magnitude = feedback_factories.ReportProblemMagnitudeFactory(label_it='Possible') - - def test_options_can_be_obtained_as_json(self): - response = self.client.get('/api/en/feedback/options.json') - self.assertEqual(response.status_code, 200) - data = response.json() - self.assertEqual(data['activities'][0]['id'], self.activity.id) - self.assertEqual(data['activities'][0]['label'], self.activity.label) - self.assertEqual(data['categories'][0]['id'], self.cat.id) - self.assertEqual(data['categories'][0]['label'], self.cat.label) - self.assertEqual(data['magnitudeProblems'][0]['id'], self.pb_magnitude.id) - self.assertEqual(data['magnitudeProblems'][0]['label'], self.pb_magnitude.label) - - def test_options_are_translated(self): - response = self.client.get('/api/it/feedback/options.json') - self.assertEqual(response.status_code, 200) - data = response.json() - self.assertEqual(data['activities'][0]['label'], self.activity.label_it) - self.assertEqual(data['categories'][0]['label'], self.cat.label_it) - self.assertEqual(data['magnitudeProblems'][0]['label'], self.pb_magnitude.label_it) - - def test_display_dates(self): - date_time_1 = datetime.strptime("24/03/21 20:51", '%d/%m/%y %H:%M') - date_time_2 = datetime.strptime("28/03/21 5:51", '%d/%m/%y %H:%M') - r = feedback_factories.ReportFactory(created_in_suricate=date_time_1, last_updated_in_suricate=date_time_2) - self.assertEqual("03/24/2021 8:51 p.m.", r.created_in_suricate_display) - self.assertEqual("03/28/2021 5:51 a.m.", r.last_updated_in_suricate_display) - - class SuricateViewPermissions(AuthentFixturesMixin, TestCase): @classmethod def setUpTestData(cls): diff --git a/geotrek/feedback/urls.py b/geotrek/feedback/urls.py index 4a9edc087f..c6a71cd694 100644 --- a/geotrek/feedback/urls.py +++ b/geotrek/feedback/urls.py @@ -1,22 +1,19 @@ from django.conf import settings from django.urls import path, register_converter from mapentity.registry import registry -from rest_framework.routers import DefaultRouter from geotrek.common.urls import LangConverter from geotrek.feedback import models as feedback_models -from .views import CategoryList, FeedbackOptionsView, ReportAPIViewSet +from geotrek.api.v2.views.feedback import ReportViewSet register_converter(LangConverter, 'lang') app_name = 'feedback' urlpatterns = [ - path('api//feedback/categories.json', CategoryList.as_view(), name="categories_json"), - path('api//feedback/options.json', FeedbackOptionsView.as_view(), name="options_json"), + # backward compatible report endpoint + path('api//reports/report', ReportViewSet.as_view({"post": "create"}), name="report"), ] -router = DefaultRouter(trailing_slash=False) -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/reports', ReportAPIViewSet, basename='report') -urlpatterns += router.urls + urlpatterns += registry.register(feedback_models.Report, menu=settings.REPORT_MODEL_ENABLED) diff --git a/geotrek/feedback/views.py b/geotrek/feedback/views.py index dda9a2ad81..1bca61dbb1 100644 --- a/geotrek/feedback/views.py +++ b/geotrek/feedback/views.py @@ -1,32 +1,17 @@ -import os - from crispy_forms.helper import FormHelper from django.conf import settings -from django.contrib.auth import get_user_model -from django.contrib.contenttypes.models import ContentType from django.contrib.gis.db.models.functions import Transform -from django.core.exceptions import ValidationError -from django.core.files import File -from django.core.mail import send_mail from django.db.models import CharField, F, Value from django.db.models.functions import Concat from django.urls.base import reverse from django.utils.translation import get_language from django.utils.translation import gettext as _ -from django.views.generic.list import ListView from mapentity import views as mapentity_views -from PIL import Image from rest_framework.authentication import (BasicAuthentication, SessionAuthentication) -from rest_framework.decorators import action -from rest_framework.parsers import FormParser, MultiPartParser -from rest_framework.permissions import AllowAny, IsAuthenticated -from rest_framework.response import Response -from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated -from geotrek.common.mixins.api import APIViewSet from geotrek.common.mixins.views import CustomColumnsMixin -from geotrek.common.models import Attachment, FileType from geotrek.common.viewsets import GeotrekMapentityViewSet from . import models as feedback_models @@ -92,41 +77,6 @@ def get_context_data(self, **kwargs): return super().get_context_data(**kwargs) -class CategoryList(mapentity_views.JSONResponseMixin, ListView): - model = feedback_models.ReportCategory - - def get_context_data(self, **kwargs): - return [{"id": c.id, "label": c.label} for c in self.object_list] - - -class FeedbackOptionsView(APIView): - permission_classes = [ - AllowAny, - ] - - def get(self, request, *args, **kwargs): - categories = feedback_models.ReportCategory.objects.all() - cat_serializer = feedback_serializers.ReportCategorySerializer( - categories, many=True - ) - activities = feedback_models.ReportActivity.objects.all() - activities_serializer = feedback_serializers.ReportActivitySerializer( - activities, many=True - ) - magnitude_problems = feedback_models.ReportProblemMagnitude.objects.all() - mag_serializer = feedback_serializers.ReportProblemMagnitudeSerializer( - magnitude_problems, many=True - ) - - options = { - "categories": cat_serializer.data, - "activities": activities_serializer.data, - "magnitudeProblems": mag_serializer.data, - } - - return Response(options) - - class ReportCreate(mapentity_views.MapEntityCreate): model = feedback_models.Report form_class = ReportForm @@ -182,76 +132,3 @@ def view_cache_key(self): self.request.user.pk if settings.SURICATE_WORKFLOW_ENABLED else '' ) return geojson_lookup - - -class ReportAPIViewSet(APIViewSet): - queryset = feedback_models.Report.objects.existing()\ - .select_related("activity", "category", "problem_magnitude", "status", "related_trek")\ - .prefetch_related("attachments") - parser_classes = [FormParser, MultiPartParser] - serializer_class = feedback_serializers.ReportAPISerializer - geojson_serializer_class = feedback_serializers.ReportAPIGeojsonSerializer - authentication_classes = [] - permission_classes = [AllowAny] - - def get_queryset(self): - queryset = super().get_queryset() - return queryset.select_related( - "activity", "category", "problem_magnitude", "status", "related_trek" - ) - - @action(detail=False, methods=["post"]) - def report(self, request, lang=None): - 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 diff --git a/geotrek/flatpages/helpers_sync.py b/geotrek/flatpages/helpers_sync.py deleted file mode 100644 index e93735b6a7..0000000000 --- a/geotrek/flatpages/helpers_sync.py +++ /dev/null @@ -1,23 +0,0 @@ -import os - -from geotrek.flatpages.models import FlatPage -from geotrek.flatpages.views import FlatPageViewSet, FlatPageMeta - -from django.db.models import Q - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_geojson(lang, FlatPageViewSet, 'flatpages.geojson', zipfile=self.global_sync.zipfile) - flatpages = FlatPage.objects.filter(published=True) - if self.global_sync.source: - flatpages = flatpages.filter(source__name__in=self.global_sync.source) - if self.global_sync.portal: - flatpages = flatpages.filter(Q(portal__name=self.global_sync.portal) | Q(portal=None)) - for flatpage in flatpages: - name = os.path.join('meta', lang, flatpage.rando_url, 'index.html') - self.global_sync.sync_view(lang, FlatPageMeta.as_view(), name, pk=flatpage.pk, - params={'rando_url': self.global_sync.rando_url}) diff --git a/geotrek/flatpages/locale/de/LC_MESSAGES/django.po b/geotrek/flatpages/locale/de/LC_MESSAGES/django.po index c22f944a57..a410923732 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 c22f944a57..a410923732 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 c22f944a57..a410923732 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 613fe6d2ab..a46595f2de 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 c22f944a57..a410923732 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 c22f944a57..a410923732 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/models.py b/geotrek/flatpages/models.py index e2cb7237a1..8a00fd9d7b 100644 --- a/geotrek/flatpages/models.py +++ b/geotrek/flatpages/models.py @@ -10,7 +10,6 @@ from bs4 import BeautifulSoup from extended_choices import Choices -from mapentity.serializers import plain_text from geotrek.common.mixins.models import TimeStampedModelMixin, BasePublishableMixin @@ -71,7 +70,7 @@ def clean(self): html_content += getattr(self, 'content_%s' % language[0], None) or '' def parse_media(self): - soup = BeautifulSoup(self.content or '', 'lxml') + soup = BeautifulSoup(self.content or '', features='html.parser') images = soup.findAll('img') results = [] for image in images: @@ -101,13 +100,5 @@ def get_update_url(self): def get_delete_url(self): return reverse('admin:flatpages_flatpage_delete', args=[self.pk]) - @property - def rando_url(self): - return 'informations/{}/'.format(self.slug) - - @property - def meta_description(self): - return plain_text(self.content)[:500] - def is_public(self): return self.any_published diff --git a/geotrek/flatpages/serializers.py b/geotrek/flatpages/serializers.py deleted file mode 100644 index d14d43cb5e..0000000000 --- a/geotrek/flatpages/serializers.py +++ /dev/null @@ -1,20 +0,0 @@ -from rest_framework import serializers as rest_serializers - -from geotrek.flatpages import models as flatpages_models -from geotrek.common.serializers import ( - TranslatedModelSerializer, BasePublishableSerializerMixin, - RecordSourceSerializer, TargetPortalSerializer -) - - -class FlatPageSerializer(BasePublishableSerializerMixin, TranslatedModelSerializer): - last_modified = rest_serializers.ReadOnlyField(source='date_update') - media = rest_serializers.ReadOnlyField(source='parse_media') - source = RecordSourceSerializer(many=True) - portal = TargetPortalSerializer(many=True) - - class Meta: - model = flatpages_models.FlatPage - fields = ('id', 'title', 'external_url', 'content', 'target', - 'last_modified', 'slug', 'media', 'source', 'portal') + \ - BasePublishableSerializerMixin.Meta.fields diff --git a/geotrek/flatpages/templates/flatpages/flatpage_meta.html b/geotrek/flatpages/templates/flatpages/flatpage_meta.html deleted file mode 100644 index 2cdd787e64..0000000000 --- a/geotrek/flatpages/templates/flatpages/flatpage_meta.html +++ /dev/null @@ -1,22 +0,0 @@ -{% load i18n %}{% get_current_language as LANGUAGE_CODE %} - - - - - {{ object.title }} - - - - - - - - - - - -

{{ object.title }}

-{{ object.content|safe }} -{{ META_TITLE }} - - diff --git a/geotrek/flatpages/tests/test_helpers_sync.py b/geotrek/flatpages/tests/test_helpers_sync.py deleted file mode 100644 index 39739994b3..0000000000 --- a/geotrek/flatpages/tests/test_helpers_sync.py +++ /dev/null @@ -1,41 +0,0 @@ -from io import StringIO -import os -from unittest.mock import patch - -from django.conf import settings -from django.test import TestCase - -from geotrek.common.tests.factories import FakeSyncCommand, RecordSourceFactory, TargetPortalFactory -from geotrek.flatpages.tests.factories import FlatPageFactory -from geotrek.flatpages.helpers_sync import SyncRando - - -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.flatpage = FlatPageFactory.create(published=True, title="test-0") - cls.source = RecordSourceFactory() - - cls.portal = TargetPortalFactory() - cls.flatpage_s_p = FlatPageFactory.create(published=True, title="test", portals=(cls.portal,), - sources=(cls.source,)) - cls.flatpage_s_p.save() - - @patch('sys.stdout', new_callable=StringIO) - def test_flatpages(self, mock_stdout): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'flatpages.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'meta', 'en', 'informations', - 'test-0', 'index.html'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_flatpages_sources_portal_filter(self, mock_stdout): - command = FakeSyncCommand(portal=self.portal.name, source=[self.source.name]) - synchro = SyncRando(command) - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'flatpages.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'meta', 'en', 'informations', - 'test', 'index.html'))) diff --git a/geotrek/flatpages/tests/test_views.py b/geotrek/flatpages/tests/test_views.py deleted file mode 100644 index e1f3a89f9a..0000000000 --- a/geotrek/flatpages/tests/test_views.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.test import TestCase - -from geotrek.flatpages.tests.factories import FlatPageFactory - - -class RESTViewsTest(TestCase): - @classmethod - def setUpTestData(cls): - FlatPageFactory.create_batch(10, published=True) - FlatPageFactory.create(published=False) - - def test_records_list(self): - response = self.client.get('/api/en/flatpages.json') - self.assertEqual(response.status_code, 200) - records = response.json() - self.assertEqual(len(records), 10) - - def test_serialized_attributes(self): - response = self.client.get('/api/en/flatpages.json') - records = response.json() - record = records[0] - self.assertEqual( - sorted(record.keys()), - sorted(['content', 'external_url', 'id', 'last_modified', - 'media', 'portal', 'publication_date', 'published', - 'published_status', 'slug', 'source', 'target', - 'title'])) diff --git a/geotrek/flatpages/urls.py b/geotrek/flatpages/urls.py deleted file mode 100644 index ece7a2dd4f..0000000000 --- a/geotrek/flatpages/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.urls import path, include, register_converter -from rest_framework.routers import DefaultRouter - -from geotrek.flatpages.views import FlatPageViewSet, FlatPageMeta -from geotrek.common.urls import LangConverter - - -""" -We don't use MapEntity for FlatPages, thus we use Django Rest Framework -without sugar. -""" -router = DefaultRouter(trailing_slash=False) -router.register('flatpages', FlatPageViewSet, basename='flatpages') - -register_converter(LangConverter, 'lang') - -app_name = 'flatpages' -urlpatterns = [ - path('api//', include(router.urls)), - path('api//flatpages//meta.html', FlatPageMeta.as_view(), name="flatpage_meta"), -] diff --git a/geotrek/flatpages/views.py b/geotrek/flatpages/views.py index 53345092a0..65ef51b272 100644 --- a/geotrek/flatpages/views.py +++ b/geotrek/flatpages/views.py @@ -1,37 +1,11 @@ -from rest_framework import permissions as rest_permissions -from rest_framework import viewsets - from django.urls import reverse_lazy -from django.db.models import Q -from django.views.generic import CreateView, UpdateView, DetailView +from django.views.generic import CreateView, UpdateView -from ..common.mixins.views import MetaMixin -from geotrek.flatpages.serializers import FlatPageSerializer from geotrek.flatpages import models as flatpages_models from .forms import FlatPageForm -class FlatPageViewSet(viewsets.ModelViewSet): - """ - A viewset for viewing and editing flat pages instances. - """ - model = flatpages_models.FlatPage - serializer_class = FlatPageSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - qs = flatpages_models.FlatPage.objects.filter(published=True) - - if self.request.GET.get('source', '') != '': - qs = qs.filter(source__name__in=self.request.GET['source'].split(',')) - - if self.request.GET.get('portal', '') != '': - qs = qs.filter(Q(portal__name=self.request.GET['portal']) | Q(portal=None)) - - return qs - - class FlatPageEditMixin: model = flatpages_models.FlatPage form_class = FlatPageForm @@ -49,8 +23,3 @@ class FlatPageCreate(FlatPageEditMixin, CreateView): class FlatPageUpdate(FlatPageEditMixin, UpdateView): pass - - -class FlatPageMeta(MetaMixin, DetailView): - model = flatpages_models.FlatPage - template_name = 'flatpages/flatpage_meta.html' diff --git a/geotrek/infrastructure/helpers_sync.py b/geotrek/infrastructure/helpers_sync.py deleted file mode 100644 index c22a52a45c..0000000000 --- a/geotrek/infrastructure/helpers_sync.py +++ /dev/null @@ -1,13 +0,0 @@ -from geotrek.infrastructure import models -from geotrek.infrastructure.views import InfrastructureAPIViewSet - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_geojson(lang, InfrastructureAPIViewSet, 'infrastructures.geojson') - self.global_sync.sync_static_file(lang, 'infrastructure/picto-infrastructure.png') - models_picto = [models.InfrastructureType] - self.global_sync.sync_pictograms(lang, models_picto, zipfile=self.global_sync.zipfile) diff --git a/geotrek/infrastructure/locale/de/LC_MESSAGES/django.po b/geotrek/infrastructure/locale/de/LC_MESSAGES/django.po index 51c293b1ec..e7bef00a4a 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 bd42f0f14b..8bdf28cbf7 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 bd42f0f14b..8bdf28cbf7 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 f1c7804574..d799cd087f 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 bd42f0f14b..8bdf28cbf7 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/tests/factories.py b/geotrek/infrastructure/tests/factories.py index 087f08a23e..5662a4abf4 100644 --- a/geotrek/infrastructure/tests/factories.py +++ b/geotrek/infrastructure/tests/factories.py @@ -1,7 +1,7 @@ import factory from geotrek.common.utils.testdata import dummy_filefield_as_sequence -from geotrek.core.tests.factories import TopologyFactory, PointTopologyFactory +from geotrek.core.tests.factories import PointTopologyFactory, TopologyFactory from .. import models diff --git a/geotrek/infrastructure/tests/test_filters.py b/geotrek/infrastructure/tests/test_filters.py index 806f9c2e89..a3a1495b85 100644 --- a/geotrek/infrastructure/tests/test_filters.py +++ b/geotrek/infrastructure/tests/test_filters.py @@ -1,13 +1,13 @@ -import datetime - from django.test import TestCase -from geotrek.authent.tests.base import AuthentFixturesTest -from geotrek.authent.tests.factories import PathManagerFactory -from geotrek.infrastructure.filters import InfrastructureFilterSet -from geotrek.infrastructure.tests.factories import (InfrastructureUsageDifficultyLevelFactory, InfrastructureFactory, - InfrastructureMaintenanceDifficultyLevelFactory) -from geotrek.maintenance.tests.factories import InterventionFactory +from geotrek.maintenance.tests.factories import InfrastructureInterventionFactory + +from ..filters import InfrastructureFilterSet +from .factories import ( + InfrastructureFactory, + InfrastructureMaintenanceDifficultyLevelFactory, + InfrastructureUsageDifficultyLevelFactory, +) class DifficultyLeversFilterTest(TestCase): @@ -24,7 +24,7 @@ def setUpTestData(cls): cls.infra_m2 = InfrastructureFactory(maintenance_difficulty=cls.maintenance_level_2) def test_filter_usages(self): - '''Test usage difficulty filter on infrastructure''' + """ Test usage difficulty filter on infrastructure """ data = { 'usage_difficulty': [self.usage_level_1.pk] } @@ -35,7 +35,7 @@ def test_filter_usages(self): self.assertNotIn(self.infra_m2, qs) def test_filter_maintenance(self): - '''Test maintenance difficulty filter on infrastructure''' + """Test maintenance difficulty filter on infrastructure""" data = { 'maintenance_difficulty': [self.maintenance_level_2.pk], @@ -47,7 +47,7 @@ def test_filter_maintenance(self): self.assertIn(self.infra_m2, qs) def test_filter_combo(self): - '''Test both usage difficulty filter and maintenance difficulty filter on infrastructure as lower bound''' + """Test both usage difficulty filter and maintenance difficulty filter on infrastructure as lower bound""" data = { 'usage_difficulty': [self.usage_level_1.pk], 'maintenance_difficulty': [self.maintenance_level_2.pk], @@ -59,94 +59,73 @@ def test_filter_combo(self): self.assertNotIn(self.infra_m2, qs) -class InfraFilterTestMixin: - factory = None - filterset = None - - def login(self): - user = PathManagerFactory(password='booh') - self.client.force_login(user=user) - - def test_intervention_filter(self): - self.login() - - model = self.factory._meta.model - # We will filter by this year - year = 2014 - good_date_year = datetime.datetime(year=year, month=2, day=2) - bad_date_year = datetime.datetime(year=year + 2, month=2, day=2) - - # Bad topology/infrastructure: No intervention - self.factory() - - # Bad signage: intervention with wrong year - bad_topo = self.factory() - InterventionFactory(target=bad_topo, begin_date=bad_date_year) - - # Good signage: intervention with the good year - good_topo = self.factory() - InterventionFactory(target=good_topo, begin_date=good_date_year) - - data = { - 'intervention_year': year - } - response = self.client.get(model.get_datatablelist_url(), data) - - self.assertEqual(response.status_code, 200) - self.assertEqual(len(response.json()['data']), 1) - - def test_duplicate_implantation_year_filter(self): - self.login() - - model = self.factory._meta.model - # We will check if this - year = 2014 - year_t = datetime.datetime(year=year, month=2, day=2) - - # Bad signage: intervention with wrong year - topo_1 = self.factory() - InterventionFactory(target=topo_1, begin_date=year_t) - - # Good signage: intervention with the good year - topo_2 = self.factory() - InterventionFactory(target=topo_2, begin_date=year_t) - - response = self.client.get(model.get_list_url()) - self.assertContains(response, '', count=1) - - -class InfrastructureFilterTest(InfraFilterTestMixin, AuthentFixturesTest): +class InfrastructureFilterTest(TestCase): factory = InfrastructureFactory filterset = InfrastructureFilterSet def test_none_implantation_year_filter(self): - self.login() - model = self.factory._meta.model - InfrastructureFactory.create() - response = self.client.get(model.get_list_url()) - self.assertFalse('option value="" selected>NoneNone2015') - self.assertContains(response, '') + self.assertIn('', form.as_p()) + self.assertIn('', form.as_p()) - filter = InfrastructureFilterSet(data={'implantation_year': [2015]}) - self.assertTrue(i in filter.qs) - self.assertFalse(i2 in filter.qs) + filterset = self.filterset(data={'implantation_year': [2015]}) + self.assertTrue(i in filterset.qs) + self.assertFalse(i2 in filterset.qs) def test_implantation_year_filter_with_str(self): - i = InfrastructureFactory.create(implantation_year=2015) - i2 = InfrastructureFactory.create(implantation_year=2016) - filter_set = InfrastructureFilterSet(data={'implantation_year': 'toto'}) + i = self.factory(implantation_year=2015) + i2 = self.factory(implantation_year=2016) + filter_set = self.filterset(data={'implantation_year': 'toto'}) filter_form = filter_set.form.as_p() self.assertIn('', filter_form) self.assertIn('', filter_form) self.assertIn(i, filter_set.qs) self.assertIn(i2, filter_set.qs) + + def test_form_should_have_signage_intervention_year_choices(self): + InfrastructureInterventionFactory(begin_date='2015-01-01') + InfrastructureInterventionFactory(begin_date='2020-01-01') + filter_set = self.filterset() + choice_values = [choice[0] for choice in filter_set.form.fields['intervention_year'].choices] + self.assertIn(2015, choice_values) + self.assertIn(2020, choice_values) + self.assertNotIn(2022, choice_values) + + def test_filter_by_intervention_year(self): + filtered_infra_intervention = InfrastructureInterventionFactory(begin_date='2015-01-01') + non_filtered_infra_intervention = InfrastructureInterventionFactory(begin_date='2020-01-01') + filter_set = self.filterset(data={'intervention_year': [2015]}) + qs = filter_set.qs + + self.assertEqual(1, len(qs)) + self.assertIn(filtered_infra_intervention.target, qs) + self.assertNotIn(non_filtered_infra_intervention.target, qs) + + def test_provider_filter_without_provider(self): + filter_set = self.filterset(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + infrastructure1 = self.factory(provider='my_provider1') + infrastructure2 = self.factory(provider='my_provider2') + + filter_set = self.filterset() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(infrastructure1, filter_set.qs) + self.assertIn(infrastructure2, filter_set.qs) diff --git a/geotrek/infrastructure/tests/test_helpers_sync.py b/geotrek/infrastructure/tests/test_helpers_sync.py deleted file mode 100644 index f5a4bf85c2..0000000000 --- a/geotrek/infrastructure/tests/test_helpers_sync.py +++ /dev/null @@ -1,26 +0,0 @@ -from io import StringIO -import os -from unittest.mock import patch - -from django.conf import settings -from django.test import TestCase - -from geotrek.infrastructure.tests.factories import InfrastructureFactory -from geotrek.infrastructure.helpers_sync import SyncRando -from geotrek.common.tests.factories import FakeSyncCommand - - -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.signage = InfrastructureFactory.create(published=True) - - @patch('sys.stdout', new_callable=StringIO) - def test_infrastructure(self, mock_stdout): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'infrastructures.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'static', 'infrastructure', - 'picto-infrastructure.png'))) diff --git a/geotrek/infrastructure/tests/test_models.py b/geotrek/infrastructure/tests/test_models.py index 2901ca26da..a5a7754eb0 100644 --- a/geotrek/infrastructure/tests/test_models.py +++ b/geotrek/infrastructure/tests/test_models.py @@ -3,8 +3,8 @@ from unittest import skipIf from geotrek.core.tests.factories import PathFactory -from geotrek.infrastructure.tests.factories import InfrastructureFactory from geotrek.zoning.tests import factories as zoning_factory +from .factories import InfrastructureFactory, InfrastructureTypeNoPictogramFactory class InfrastructureTest(TestCase): @@ -47,3 +47,10 @@ def test_display_cities(self): # Case : return only city3 self.assertEqual(self.infra_cities3.cities_display, "city3") + + +class InfrastructureTypeTesCase(TestCase): + def test_pictogram_url_default(self): + """Return infrastructure default pictogram if not defined""" + infrastructure_type = InfrastructureTypeNoPictogramFactory() + self.assertEqual(infrastructure_type.get_pictogram_url(), '/static/infrastructure/picto-infrastructure.png') diff --git a/geotrek/infrastructure/tests/test_views.py b/geotrek/infrastructure/tests/test_views.py index b01b336684..426d95086a 100755 --- a/geotrek/infrastructure/tests/test_views.py +++ b/geotrek/infrastructure/tests/test_views.py @@ -1,16 +1,18 @@ from django.conf import settings -from geotrek.common.tests import CommonTest, GeotrekAPITestCase from geotrek.authent.tests.factories import PathManagerFactory -from geotrek.infrastructure.models import (Infrastructure, INFRASTRUCTURE_TYPES) -from geotrek.infrastructure.filters import InfrastructureFilterSet +from geotrek.common.tests import CommonTest from geotrek.core.tests.factories import PathFactory -from geotrek.infrastructure.tests.factories import (InfrastructureFactory, InfrastructureNoPictogramFactory, - InfrastructureTypeFactory, InfrastructureConditionFactory, - PointInfrastructureFactory) +from geotrek.infrastructure.models import INFRASTRUCTURE_TYPES, Infrastructure +from geotrek.infrastructure.tests.factories import ( + InfrastructureConditionFactory, + InfrastructureFactory, + InfrastructureTypeFactory, + PointInfrastructureFactory, +) -class InfrastructureViewsTest(GeotrekAPITestCase, CommonTest): +class InfrastructureViewsTest(CommonTest): model = Infrastructure modelfactory = InfrastructureFactory userfactory = PathManagerFactory @@ -25,30 +27,8 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'accessibility': '', 'name': self.obj.name, - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False} - ], - 'structure': { - 'id': self.obj.structure.pk, - 'name': self.obj.structure.name, - }, - 'type': { - 'id': self.obj.type.pk, - 'label': self.obj.type.label, - 'pictogram': self.obj.type.pictogram.url if self.obj.type.pictogram else '/static/infrastructure/picto-infrastructure.png', - }, + 'published': self.obj.published, } def get_expected_datatables_attrs(self): @@ -90,10 +70,6 @@ def test_check_structure_or_none_related_are_visible(self): type = form.fields['type'] self.assertTrue((infratype.pk, str(infratype)) in type.choices) - def test_no_pictogram(self): - self.modelfactory = InfrastructureNoPictogramFactory - super().test_api_detail_for_model() - class PointInfrastructureViewsTest(InfrastructureViewsTest): modelfactory = PointInfrastructureFactory @@ -117,28 +93,3 @@ def get_good_data(self): else: good_data['geom'] = 'POINT(0.42 0.666)' return good_data - - -class InfrastructureFilterTest(CommonTest): - factory = InfrastructureFactory - filterset = InfrastructureFilterSet - - def test_provider_filter_without_provider(self): - filter_set = InfrastructureFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - infrastructure1 = InfrastructureFactory.create(provider='my_provider1') - infrastructure2 = InfrastructureFactory.create(provider='my_provider2') - - filter_set = InfrastructureFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(infrastructure1, filter_set.qs) - self.assertIn(infrastructure2, filter_set.qs) diff --git a/geotrek/infrastructure/urls.py b/geotrek/infrastructure/urls.py index 2010b4c0da..c1089d742f 100644 --- a/geotrek/infrastructure/urls.py +++ b/geotrek/infrastructure/urls.py @@ -1,13 +1,10 @@ from django.conf import settings from django.urls import path, register_converter - from mapentity.registry import registry -from rest_framework.routers import DefaultRouter -from . import models -from geotrek.trekking.views import TrekInfrastructureViewSet from geotrek.common.urls import LangConverter -from .views import InfrastructureAPIViewSet +from geotrek.trekking.views import TrekInfrastructureViewSet +from . import models register_converter(LangConverter, 'lang') @@ -15,11 +12,6 @@ urlpatterns = registry.register(models.Infrastructure, menu=settings.INFRASTRUCTURE_MODEL_ENABLED) -router = DefaultRouter(trailing_slash=False) - - -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/infrastructures', InfrastructureAPIViewSet, basename='infrastructrure') -urlpatterns += router.urls urlpatterns += [ path('api//treks//infrastructures.geojson', diff --git a/geotrek/infrastructure/views.py b/geotrek/infrastructure/views.py index 75f2a065a2..c86e71ed69 100755 --- a/geotrek/infrastructure/views.py +++ b/geotrek/infrastructure/views.py @@ -1,19 +1,25 @@ from django.conf import settings from django.contrib.gis.db.models.functions import Transform -from mapentity.views import (MapEntityList, MapEntityFormat, MapEntityDetail, MapEntityDocument, - MapEntityCreate, MapEntityUpdate, MapEntityDelete) +from mapentity.views import ( + MapEntityCreate, + MapEntityDelete, + MapEntityDetail, + MapEntityDocument, + MapEntityFormat, + MapEntityList, + MapEntityUpdate, +) from geotrek.authent.decorators import same_structure_required -from geotrek.common.mixins.api import APIViewSet from geotrek.common.mixins.views import CustomColumnsMixin from geotrek.common.viewsets import GeotrekMapentityViewSet from geotrek.core.models import AltimetryMixin from geotrek.core.views import CreateFromTopologyMixin + from .filters import InfrastructureFilterSet from .forms import InfrastructureForm from .models import Infrastructure -from .serializers import InfrastructureSerializer, InfrastructureAPIGeojsonSerializer, InfrastructureAPISerializer, \ - InfrastructureGeojsonSerializer +from .serializers import InfrastructureGeojsonSerializer, InfrastructureSerializer class InfrastructureList(CustomColumnsMixin, MapEntityList): @@ -84,12 +90,3 @@ def get_queryset(self): else: qs = qs.select_related('type', 'condition', 'maintenance_difficulty', 'access', 'usage_difficulty') return qs - - -class InfrastructureAPIViewSet(APIViewSet): - model = Infrastructure - serializer_class = InfrastructureAPISerializer - geojson_serializer_class = InfrastructureAPIGeojsonSerializer - - def get_queryset(self): - return Infrastructure.objects.existing().filter(published=True).annotate(api_geom=Transform("geom", settings.API_SRID)) diff --git a/geotrek/land/locale/de/LC_MESSAGES/django.po b/geotrek/land/locale/de/LC_MESSAGES/django.po index 30eb0a3ade..4d07022e6c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 30eb0a3ade..4d07022e6c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 30eb0a3ade..4d07022e6c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 65eee4d2f8..af0bb7ab0f 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 30eb0a3ade..4d07022e6c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 30eb0a3ade..4d07022e6c 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 5c6ebb762e..14765c80fd 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 5c6ebb762e..14765c80fd 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 5c6ebb762e..14765c80fd 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 906baf1dc1..925d4479bd 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 5c6ebb762e..14765c80fd 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 efea3f24f8..b2f94e30e3 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -116,6 +116,9 @@ msgstr "" msgid "Project" msgstr "" +msgid "End date" +msgstr "" + msgid "Mandays" msgstr "" @@ -143,9 +146,6 @@ msgstr "" msgid "Begin date" msgstr "" -msgid "End date" -msgstr "" - msgid "Subcontracting" msgstr "" diff --git a/geotrek/maintenance/locale/en/LC_MESSAGES/django.po b/geotrek/maintenance/locale/en/LC_MESSAGES/django.po index efea3f24f8..b2f94e30e3 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -116,6 +116,9 @@ msgstr "" msgid "Project" msgstr "" +msgid "End date" +msgstr "" + msgid "Mandays" msgstr "" @@ -143,9 +146,6 @@ msgstr "" msgid "Begin date" msgstr "" -msgid "End date" -msgstr "" - msgid "Subcontracting" msgstr "" diff --git a/geotrek/maintenance/locale/es/LC_MESSAGES/django.po b/geotrek/maintenance/locale/es/LC_MESSAGES/django.po index efea3f24f8..b2f94e30e3 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -116,6 +116,9 @@ msgstr "" msgid "Project" msgstr "" +msgid "End date" +msgstr "" + msgid "Mandays" msgstr "" @@ -143,9 +146,6 @@ msgstr "" msgid "Begin date" msgstr "" -msgid "End date" -msgstr "" - msgid "Subcontracting" msgstr "" diff --git a/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po b/geotrek/maintenance/locale/fr/LC_MESSAGES/django.po index 40bf900f88..3b7d96bb8e 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -115,6 +115,9 @@ msgstr "Longueur" msgid "Project" msgstr "Chantier" +msgid "End date" +msgstr "Date de fin" + msgid "Mandays" msgstr "Jours-Hommes" @@ -142,9 +145,6 @@ msgstr "Bref résumé" msgid "Begin date" msgstr "Date de début" -msgid "End date" -msgstr "Date de fin" - msgid "Subcontracting" msgstr "Sous-traitance" diff --git a/geotrek/maintenance/locale/it/LC_MESSAGES/django.po b/geotrek/maintenance/locale/it/LC_MESSAGES/django.po index efea3f24f8..b2f94e30e3 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -116,6 +116,9 @@ msgstr "" msgid "Project" msgstr "" +msgid "End date" +msgstr "" + msgid "Mandays" msgstr "" @@ -143,9 +146,6 @@ msgstr "" msgid "Begin date" msgstr "" -msgid "End date" -msgstr "" - msgid "Subcontracting" msgstr "" diff --git a/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po b/geotrek/maintenance/locale/nl/LC_MESSAGES/django.po index efea3f24f8..b2f94e30e3 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -116,6 +116,9 @@ msgstr "" msgid "Project" msgstr "" +msgid "End date" +msgstr "" + msgid "Mandays" msgstr "" @@ -143,9 +146,6 @@ msgstr "" msgid "Begin date" msgstr "" -msgid "End date" -msgstr "" - msgid "Subcontracting" msgstr "" diff --git a/geotrek/outdoor/locale/de/LC_MESSAGES/django.po b/geotrek/outdoor/locale/de/LC_MESSAGES/django.po index 3442fb590b..44460b0f1b 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/en/LC_MESSAGES/django.po b/geotrek/outdoor/locale/en/LC_MESSAGES/django.po index 3442fb590b..44460b0f1b 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/es/LC_MESSAGES/django.po b/geotrek/outdoor/locale/es/LC_MESSAGES/django.po index 3442fb590b..44460b0f1b 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/fr/LC_MESSAGES/django.po b/geotrek/outdoor/locale/fr/LC_MESSAGES/django.po index 6f9166a76a..b827c321ce 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/it/LC_MESSAGES/django.po b/geotrek/outdoor/locale/it/LC_MESSAGES/django.po index 3442fb590b..44460b0f1b 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/nl/LC_MESSAGES/django.po b/geotrek/outdoor/locale/nl/LC_MESSAGES/django.po index 3442fb590b..44460b0f1b 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/parsers.py b/geotrek/outdoor/parsers.py index 0576986046..1f16ccb920 100644 --- a/geotrek/outdoor/parsers.py +++ b/geotrek/outdoor/parsers.py @@ -68,8 +68,8 @@ def get_id_from_mapping(self, mapping, value): return dest_id return None - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def start(self): + super().start() self.init_outdoor_category('sector', Sector) self.init_outdoor_category('practice', Practice, join_field='sector') self.init_outdoor_category('scale', RatingScale, join_field='practice') @@ -146,9 +146,9 @@ def filter_type(self, src, val): return SiteType.objects.get(pk=type_id) return None - def parse(self, filename=None, limit=None): + def start(self): + super().start() self.init_outdoor_category('type', SiteType, join_field='practice') - super().parse(filename, limit) def parse_row(self, row): super().parse_row(row) @@ -229,9 +229,9 @@ def filter_points_reference(self, src, val): return str(val) return None - def parse(self, filename=None, limit=None): + def start(self): + super().start() self.init_outdoor_category('type', CourseType, join_field='practice') - super().parse(filename, limit) def parse_row(self, row): super().parse_row(row) diff --git a/geotrek/outdoor/serializers.py b/geotrek/outdoor/serializers.py index 920c0927b9..da7f9e81db 100644 --- a/geotrek/outdoor/serializers.py +++ b/geotrek/outdoor/serializers.py @@ -1,22 +1,7 @@ -import json - -from django.conf import settings from drf_dynamic_fields import DynamicFieldsMixin from mapentity.serializers import MapentityGeojsonModelSerializer from rest_framework import serializers -from rest_framework_gis.fields import GeometryField -from rest_framework_gis.serializers import GeoFeatureModelSerializer -from geotrek.authent.serializers import StructureSerializer -from geotrek.common.serializers import (LabelSerializer, - PublishableSerializerMixin, - RecordSourceSerializer, - TargetPortalSerializer, - ThemeSerializer, - TranslatedModelSerializer) -from geotrek.tourism.serializers import InformationDeskSerializer -from geotrek.trekking.serializers import WebLinkSerializer -from geotrek.zoning.serializers import ZoningAPISerializerMixin from .models import Course, Site, Practice @@ -48,37 +33,6 @@ class Meta(MapentityGeojsonModelSerializer.Meta): fields = ["id", "name"] -class SiteAPISerializer(PublishableSerializerMixin, ZoningAPISerializerMixin, TranslatedModelSerializer): - practice = PracticeSerializer() - structure = StructureSerializer() - labels = LabelSerializer(many=True) - themes = ThemeSerializer(many=True) - portal = TargetPortalSerializer(many=True) - source = RecordSourceSerializer(many=True) - information_desks = InformationDeskSerializer(many=True) - web_links = WebLinkSerializer(many=True) - type = SiteTypeSerializer() - children = serializers.ReadOnlyField(source='published_children') - - class Meta: - model = Site - fields = ( - 'id', 'structure', 'name', 'practice', 'accessibility', 'description', 'description_teaser', - 'ambiance', 'advice', 'period', 'labels', 'themes', 'portal', 'source', - 'information_desks', 'web_links', 'type', 'parent', 'children', 'eid', - 'orientation', 'wind', 'ratings' - ) + ZoningAPISerializerMixin.Meta.fields + PublishableSerializerMixin.Meta.fields - - -class SiteAPIGeojsonSerializer(GeoFeatureModelSerializer, SiteAPISerializer): - # Annotated geom field with API_SRID - api_geom = GeometryField(read_only=True, precision=7) - - class Meta(SiteAPISerializer.Meta): - geo_field = 'api_geom' - fields = SiteAPISerializer.Meta.fields + ('api_geom', ) - - class CourseSerializer(DynamicFieldsMixin, serializers.ModelSerializer): structure = serializers.SlugRelatedField('name', read_only=True) parent_sites = serializers.CharField(source='parent_sites_display') @@ -93,30 +47,3 @@ class CourseGeojsonSerializer(MapentityGeojsonModelSerializer): class Meta(MapentityGeojsonModelSerializer.Meta): model = Course fields = ["id", "name"] - - -class CourseAPISerializer(PublishableSerializerMixin, ZoningAPISerializerMixin, TranslatedModelSerializer): - structure = StructureSerializer() - points_reference = serializers.SerializerMethodField() - - class Meta: - model = Course - fields = ( - 'id', 'structure', 'name', 'parent_sites', 'description', 'duration', 'advice', 'points_reference', - 'equipment', 'accessibility', 'height', 'eid', 'ratings', 'ratings_description', 'gear', 'type' - ) + ZoningAPISerializerMixin.Meta.fields + PublishableSerializerMixin.Meta.fields - - def get_points_reference(self, obj): - if not obj.points_reference: - return None - geojson = obj.points_reference.transform(settings.API_SRID, clone=True).geojson - return json.loads(geojson) - - -class CourseAPIGeojsonSerializer(GeoFeatureModelSerializer, CourseAPISerializer): - # Annotated geom field with API_SRID - api_geom = GeometryField(read_only=True, precision=7) - - class Meta(CourseAPISerializer.Meta): - geo_field = 'api_geom' - fields = CourseAPISerializer.Meta.fields + ('api_geom', ) diff --git a/geotrek/outdoor/tests/test_filters.py b/geotrek/outdoor/tests/test_filters.py index 1d848c6ebc..b18476c804 100644 --- a/geotrek/outdoor/tests/test_filters.py +++ b/geotrek/outdoor/tests/test_filters.py @@ -2,8 +2,10 @@ from django.test import TestCase from geotrek.common.tests.factories import OrganismFactory -from geotrek.outdoor.tests.factories import SiteFactory, PracticeFactory, CourseFactory -from geotrek.outdoor.filters import SiteFilterSet, CourseFilterSet + +from ..filters import CourseFilterSet, SiteFilterSet +from ..models import Course, Site +from .factories import CourseFactory, PracticeFactory, SiteFactory class SiteFilterSetTest(TestCase): @@ -58,6 +60,26 @@ def test_managers_filter_or(self): self.assertEqual(len(filterset.qs), 2) self.assertListEqual(list(filterset.qs.values_list('pk', flat=True)), [self.parent.pk, self.child.pk]) + def test_provider_filter_without_provider(self): + filter_set = SiteFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(Site.objects.filter(provider="").count(), filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + site1 = SiteFactory.create(provider='my_provider1') + site2 = SiteFactory.create(provider='my_provider2') + + filter_set = SiteFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(site1, filter_set.qs) + self.assertIn(site2, filter_set.qs) + class CourseFilterSetTest(TestCase): @classmethod @@ -82,3 +104,23 @@ def test_orientation_filter_or(self): queryset = filterset.qs self.assertEqual(len(queryset), 2) self.assertListEqual(list(queryset), [self.course1, self.course2]) + + def test_provider_filter_without_provider(self): + filter_set = CourseFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(Course.objects.filter(provider="").count(), filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + course1 = CourseFactory.create(provider='my_provider1') + course2 = CourseFactory.create(provider='my_provider2') + + filter_set = CourseFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(course1, filter_set.qs) + self.assertIn(course2, filter_set.qs) diff --git a/geotrek/outdoor/tests/test_functional.py b/geotrek/outdoor/tests/test_functional.py index 9f0e0dacac..cd221ea2da 100644 --- a/geotrek/outdoor/tests/test_functional.py +++ b/geotrek/outdoor/tests/test_functional.py @@ -1,4 +1,4 @@ -from geotrek.common.tests import CommonTest, GeotrekAPITestCase +from geotrek.common.tests import CommonTest from geotrek.outdoor.models import Site, Course from geotrek.outdoor.tests.factories import SiteFactory, CourseFactory, OutdoorManagerFactory from geotrek.authent.tests.factories import StructureFactory @@ -7,7 +7,7 @@ from django.utils.translation import gettext as _ -class SiteViewsTests(GeotrekAPITestCase, CommonTest): +class SiteViewsTests(CommonTest): model = Site modelfactory = SiteFactory userfactory = OutdoorManagerFactory @@ -26,56 +26,6 @@ def get_expected_geojson_attrs(self): 'name': self.obj.name } - def get_expected_json_attrs(self): - return { - 'accessibility': 'Accessible', - 'advice': 'Warning!', - 'ambiance': 'Party time!', - 'areas': [], - 'children': [], - 'cities': [], - 'description': 'Blah', - 'description_teaser': 'More blah', - 'districts': [], - 'eid': '42', - 'filelist_url': '/paperclip/get/outdoor/site/{}/'.format(self.obj.pk), - 'information_desks': [], - 'labels': [], - 'map_image_url': '/image/site-{}.png'.format(self.obj.pk), - 'name': 'Site', - 'orientation': ['S', 'SW'], - 'parent': None, - 'period': 'Summer', - 'portal': [], - 'practice': { - 'id': self.obj.practice.pk, - 'name': 'Practice', - }, - 'printable': '/api/en/sites/{}/site.pdf'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False}, - ], - 'slug': 'site', - 'source': [], - 'structure': { - 'id': self.obj.structure.pk, - 'name': 'My structure', - }, - 'themes': [], - 'type': { - 'id': self.obj.type.pk, - 'name': 'Site type' - }, - 'web_links': [], - 'ratings': [], - 'wind': ['N'], - } - def get_bad_data(self): return { 'geom': 'doh!' @@ -114,7 +64,7 @@ def test_custom_columns_mixin_on_export(self): ['id', 'orientation', 'ratings', 'period']) -class CourseViewsTests(GeotrekAPITestCase, CommonTest): +class CourseViewsTests(CommonTest): model = Course modelfactory = CourseFactory userfactory = OutdoorManagerFactory @@ -133,43 +83,6 @@ def get_expected_geojson_attrs(self): 'name': self.obj.name } - def get_expected_json_attrs(self): - return { - 'advice': 'Warning!', - 'areas': [], - 'cities': [], - 'description': 'Blah', - 'districts': [], - 'duration': 55.0, - 'eid': '43', - 'equipment': 'Rope', - 'accessibility': 'Accessible', - 'filelist_url': '/paperclip/get/outdoor/course/{}/'.format(self.obj.pk), - 'gear': 'Shoes mandatory', - 'height': 42, - 'map_image_url': '/image/course-{}.png'.format(self.obj.pk), - 'name': 'Course', - 'parent_sites': [self.obj.parent_sites.first().pk], - 'points_reference': None, - 'printable': '/api/en/courses/{}/course.pdf'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False}, - ], - 'slug': 'course', - 'structure': { - 'id': self.obj.structure.pk, - 'name': 'My structure', - }, - 'type': self.obj.type.pk, - 'ratings': [], - 'ratings_description': 'Ths rating is ratable', - } - def get_expected_datatables_attrs(self): return { 'date_update': '17/03/2020 00:00:00', diff --git a/geotrek/outdoor/tests/test_parsers.py b/geotrek/outdoor/tests/test_parsers.py index 87adfcefe6..5f4b25d386 100644 --- a/geotrek/outdoor/tests/test_parsers.py +++ b/geotrek/outdoor/tests/test_parsers.py @@ -30,23 +30,23 @@ def test_geotrek_aggregator_parser_mapping(self, mocked_head, mocked_get): ('outdoor', 'label.json'), ('outdoor', 'source.json'), ('outdoor', 'organism.json'), - ('outdoor', 'outdoor_sector.json'), - ('outdoor', 'outdoor_practice.json'), - ('outdoor', 'outdoor_ratingscale.json'), - ('outdoor', 'outdoor_rating.json'), ('outdoor', 'theme.json'), ('outdoor', 'label.json'), ('outdoor', 'source.json'), ('outdoor', 'organism.json'), + ('outdoor', 'outdoor_site_ids.json'), ('outdoor', 'outdoor_sector.json'), ('outdoor', 'outdoor_practice.json'), ('outdoor', 'outdoor_ratingscale.json'), ('outdoor', 'outdoor_rating.json'), ('outdoor', 'outdoor_sitetype.json'), - ('outdoor', 'outdoor_site_ids.json'), ('outdoor', 'outdoor_site.json'), - ('outdoor', 'outdoor_coursetype.json'), ('outdoor', 'outdoor_course_ids.json'), + ('outdoor', 'outdoor_sector.json'), + ('outdoor', 'outdoor_practice.json'), + ('outdoor', 'outdoor_ratingscale.json'), + ('outdoor', 'outdoor_rating.json'), + ('outdoor', 'outdoor_coursetype.json'), ('outdoor', 'outdoor_course.json')] # Mock GET @@ -87,23 +87,23 @@ def test_create_sites_and_courses(self, mocked_head, mocked_get): ('outdoor', 'label.json'), ('outdoor', 'source.json'), ('outdoor', 'organism.json'), - ('outdoor', 'outdoor_sector.json'), - ('outdoor', 'outdoor_practice.json'), - ('outdoor', 'outdoor_ratingscale.json'), - ('outdoor', 'outdoor_rating.json'), ('outdoor', 'theme.json'), ('outdoor', 'label.json'), ('outdoor', 'source.json'), ('outdoor', 'organism.json'), + ('outdoor', 'outdoor_site_ids.json'), ('outdoor', 'outdoor_sector.json'), ('outdoor', 'outdoor_practice.json'), ('outdoor', 'outdoor_ratingscale.json'), ('outdoor', 'outdoor_rating.json'), ('outdoor', 'outdoor_sitetype.json'), - ('outdoor', 'outdoor_site_ids.json'), ('outdoor', 'outdoor_site.json'), - ('outdoor', 'outdoor_coursetype.json'), ('outdoor', 'outdoor_course_ids.json'), + ('outdoor', 'outdoor_sector.json'), + ('outdoor', 'outdoor_practice.json'), + ('outdoor', 'outdoor_ratingscale.json'), + ('outdoor', 'outdoor_rating.json'), + ('outdoor', 'outdoor_coursetype.json'), ('outdoor', 'outdoor_course.json')] # Mock GET @@ -238,23 +238,23 @@ def test_create_sites_and_courses_with_wrong_children(self, mocked_head, mocked_ ('outdoor', 'label.json'), ('outdoor', 'source.json'), ('outdoor', 'organism.json'), - ('outdoor', 'outdoor_sector.json'), - ('outdoor', 'outdoor_practice.json'), - ('outdoor', 'outdoor_ratingscale.json'), - ('outdoor', 'outdoor_rating.json'), ('outdoor', 'theme.json'), ('outdoor', 'label.json'), ('outdoor', 'source.json'), ('outdoor', 'organism.json'), + ('outdoor', 'outdoor_site_ids.json'), ('outdoor', 'outdoor_sector.json'), ('outdoor', 'outdoor_practice.json'), ('outdoor', 'outdoor_ratingscale.json'), ('outdoor', 'outdoor_rating.json'), ('outdoor', 'outdoor_sitetype.json'), - ('outdoor', 'outdoor_site_ids.json'), ('outdoor', 'outdoor_site_wrong_children.json'), - ('outdoor', 'outdoor_coursetype.json'), ('outdoor', 'outdoor_course_ids.json'), + ('outdoor', 'outdoor_sector.json'), + ('outdoor', 'outdoor_practice.json'), + ('outdoor', 'outdoor_ratingscale.json'), + ('outdoor', 'outdoor_rating.json'), + ('outdoor', 'outdoor_coursetype.json'), ('outdoor', 'outdoor_course_wrong_children.json')] # Mock GET diff --git a/geotrek/outdoor/tests/test_views.py b/geotrek/outdoor/tests/test_views.py index 64d0dc78fb..1e15055dde 100644 --- a/geotrek/outdoor/tests/test_views.py +++ b/geotrek/outdoor/tests/test_views.py @@ -1,17 +1,12 @@ from unittest import mock -from django.contrib.gis.geos.collections import MultiPoint -from django.contrib.gis.geos.point import Point from django.test import RequestFactory, TestCase from django.test.utils import override_settings from django.urls import reverse from mapentity.tests.factories import SuperUserFactory -from geotrek.common.tests.factories import (RecordSourceFactory, - TargetPortalFactory) from geotrek.outdoor import views as course_views from geotrek.outdoor.models import Site -from geotrek.outdoor.filters import SiteFilterSet, CourseFilterSet from geotrek.outdoor.tests.factories import CourseFactory, SiteFactory from geotrek.tourism.tests.test_views import PNG_BLACK_PIXEL from geotrek.trekking.tests.factories import POIFactory @@ -27,30 +22,6 @@ def test_public_document_pdf(self, mocked): response = self.client.get(url) self.assertEqual(response.status_code, 200) - def test_api_filters(self): - SiteFactory.create(name='site1', published=False) - SiteFactory.create(name='site2', published=True) - site3 = SiteFactory.create(name='site3', published=True) - - site3.source.add(RecordSourceFactory.create(name='source1')) - site3.portal.add(TargetPortalFactory.create(name='portal1')) - - response1 = self.client.get('/api/en/sites.json') - self.assertEqual(len(response1.json()), 2) - self.assertEqual(set((site['name'] for site in response1.json())), set(('site2', 'site3'))) - - response2 = self.client.get('/api/en/sites.json?source=source1') - self.assertEqual(len(response2.json()), 1) - self.assertEqual(response2.json()[0]['name'], 'site3') - - response3 = self.client.get('/api/en/sites.json?portal=portal1') - self.assertEqual(len(response3.json()), 2) - self.assertEqual(set((site['name'] for site in response3.json())), set(('site2', 'site3'))) - - response4 = self.client.get('/api/en/sites.json?portal=portalX') - self.assertEqual(len(response4.json()), 1) - self.assertEqual(response4.json()[0]['name'], 'site2') - @override_settings(TREK_EXPORT_POI_LIST_LIMIT=1) @mock.patch('mapentity.models.MapEntityMixin.prepare_map_image') @mock.patch('mapentity.models.MapEntityMixin.get_attributes_html') @@ -101,38 +72,6 @@ def test_course_export_poi_list_limit(self, mocked_prepare, mocked_attributes): context = view.get_context_data() self.assertEqual(len(context['pois']), 1) - def test_api_filters(self): - CourseFactory.create(name='course1', published=False) - CourseFactory.create(name='course2', published=True) - course3 = CourseFactory.create(name='course3', published=True) - course3.parent_sites.first().source.add(RecordSourceFactory.create(name='source1')) - course3.parent_sites.first().portal.add(TargetPortalFactory.create(name='portal1')) - - response1 = self.client.get('/api/en/courses.json') - self.assertEqual(len(response1.json()), 2) - self.assertEqual(set((course['name'] for course in response1.json())), set(('course2', 'course3'))) - - response2 = self.client.get('/api/en/courses.json?source=source1') - self.assertEqual(len(response2.json()), 1) - self.assertEqual(response2.json()[0]['name'], 'course3') - - response3 = self.client.get('/api/en/courses.json?portal=portal1') - self.assertEqual(len(response3.json()), 2) - self.assertEqual(set((course['name'] for course in response3.json())), set(('course2', 'course3'))) - - response4 = self.client.get('/api/en/courses.json?portal=portalX') - self.assertEqual(len(response4.json()), 1) - self.assertEqual(response4.json()[0]['name'], 'course2') - - @override_settings(API_SRID=2154) - def test_serialize_ref_points(self): - CourseFactory.create(name='course_with_ref_points', published=True, points_reference=MultiPoint(Point(12, 12))) - response = self.client.get('/api/en/courses.json') - self.assertEqual(len(response.json()), 1) - self.assertEqual(response.json()[0]['name'], 'course_with_ref_points') - data = "{'type': 'MultiPoint', 'coordinates': [[12.0, 12.0]]}" - self.assertEqual(str(response.json()[0]['points_reference']), data) - class SiteDeleteTest(TestCase): @classmethod @@ -162,53 +101,3 @@ def test_delete_site(self): self.assertEqual(response.status_code, 302) self.assertEqual(Site.objects.count(), 1) self.assertEqual(Site.objects.filter(pk=site_1.pk).exists(), True) - - -class SiteFilterTest(TestCase): - factory = SiteFactory - filterset = SiteFilterSet - - def test_provider_filter_without_provider(self): - filter_set = SiteFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - site1 = SiteFactory.create(provider='my_provider1') - site2 = SiteFactory.create(provider='my_provider2') - - filter_set = SiteFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(site1, filter_set.qs) - self.assertIn(site2, filter_set.qs) - - -class CourseFilterTest(TestCase): - factory = CourseFactory - filterset = CourseFilterSet - - def test_provider_filter_without_provider(self): - filter_set = CourseFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - course1 = CourseFactory.create(provider='my_provider1') - course2 = CourseFactory.create(provider='my_provider2') - - filter_set = CourseFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(course1, filter_set.qs) - self.assertIn(course2, filter_set.qs) diff --git a/geotrek/outdoor/urls.py b/geotrek/outdoor/urls.py index 6173d35e33..59a5b65bf5 100644 --- a/geotrek/outdoor/urls.py +++ b/geotrek/outdoor/urls.py @@ -1,5 +1,4 @@ from django.conf import settings -from rest_framework.routers import DefaultRouter from geotrek.common.urls import PublishableEntityOptions from geotrek.outdoor import models as outdoor_models @@ -8,12 +7,8 @@ app_name = 'outdoor' -urlpatterns = [] - -router = DefaultRouter(trailing_slash=False) -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/courses', outdoor_views.CourseAPIViewSet, basename='course') -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/sites', outdoor_views.SiteAPIViewSet, basename='site') +urlpatterns = [] class SiteEntityOptions(PublishableEntityOptions): @@ -28,7 +23,6 @@ class CourseEntityOptions(PublishableEntityOptions): markup_public_view = outdoor_views.CourseMarkupPublic -urlpatterns += router.urls urlpatterns += registry.register(outdoor_models.Site, SiteEntityOptions, menu=settings.SITE_MODEL_ENABLED) urlpatterns += registry.register(outdoor_models.Course, CourseEntityOptions, diff --git a/geotrek/outdoor/views.py b/geotrek/outdoor/views.py index 00e2e30a1b..d44c0c407f 100644 --- a/geotrek/outdoor/views.py +++ b/geotrek/outdoor/views.py @@ -1,22 +1,19 @@ from django.conf import settings from django.contrib.gis.db.models.functions import Transform -from django.db.models import Q, Prefetch +from django.db.models import Prefetch from geotrek.common.models import HDViewPoint from mapentity.helpers import alphabet_enumeration from mapentity.views import (MapEntityList, MapEntityDetail, MapEntityDocument, MapEntityCreate, MapEntityUpdate, MapEntityDelete, MapEntityFormat) from geotrek.authent.decorators import same_structure_required -from geotrek.common.mixins.api import APIViewSet from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin from geotrek.common.views import DocumentBookletPublic, DocumentPublic, MarkupPublic from geotrek.common.viewsets import GeotrekMapentityViewSet from .filters import SiteFilterSet, CourseFilterSet from .forms import SiteForm, CourseForm from .models import Site, Course -from .serializers import SiteSerializer, CourseSerializer, CourseAPISerializer, \ - CourseAPIGeojsonSerializer, SiteAPISerializer, SiteAPIGeojsonSerializer, SiteGeojsonSerializer, \ - CourseGeojsonSerializer +from .serializers import SiteSerializer, CourseSerializer, SiteGeojsonSerializer, CourseGeojsonSerializer class SiteList(CustomColumnsMixin, MapEntityList): @@ -131,20 +128,6 @@ def get_queryset(self): return qs -class SiteAPIViewSet(APIViewSet): - model = Site - serializer_class = SiteAPISerializer - geojson_serializer_class = SiteAPIGeojsonSerializer - - def get_queryset(self): - qs = Site.objects.filter(published=True) - if 'source' in self.request.GET: - qs = qs.filter(source__name__in=self.request.GET['source'].split(',')) - if 'portal' in self.request.GET: - qs = qs.filter(Q(portal__name=self.request.GET['portal']) | Q(portal=None)) - return qs.annotate(api_geom=Transform("geom", settings.API_SRID)) - - class CourseList(CustomColumnsMixin, MapEntityList): queryset = Course.objects.select_related('type').prefetch_related('parent_sites').all() filterform = CourseFilterSet @@ -247,17 +230,3 @@ def get_queryset(self): else: qs = qs.prefetch_related('parent_sites') return qs - - -class CourseAPIViewSet(APIViewSet): - model = Course - serializer_class = CourseAPISerializer - geojson_serializer_class = CourseAPIGeojsonSerializer - - def get_queryset(self): - qs = Course.objects.filter(published=True) - if 'source' in self.request.GET: - qs = qs.filter(parent_sites__source__name__in=self.request.GET['source'].split(',')) - if 'portal' in self.request.GET: - qs = qs.filter(Q(parent_sites__portal__name=self.request.GET['portal']) | Q(parent_sites__portal=None)) - return qs.annotate(api_geom=Transform("geom", settings.API_SRID)) diff --git a/geotrek/sensitivity/forms.py b/geotrek/sensitivity/forms.py index ee90835fe6..97bc2bd1d9 100644 --- a/geotrek/sensitivity/forms.py +++ b/geotrek/sensitivity/forms.py @@ -56,8 +56,9 @@ class Meta: widgets = {'geom': PolygonMapWidget()} def __init__(self, *args, **kwargs): - if kwargs['instance']: - species = kwargs['instance'].species + instance = kwargs.get('instance') + if instance: + species = instance.species kwargs['initial'] = { 'name': species.name, 'elevation': species.radius, @@ -68,10 +69,11 @@ def __init__(self, *args, **kwargs): for p in range(1, 13): name = 'period{:02}'.format(p) kwargs['initial'][name] = getattr(species, name) + super().__init__(*args, **kwargs) - self.helper.form_action += '?category=2' + self.helper.form_action += f'?category={Species.REGULATORY}' - def save(self): + def save(self, **kwargs): if not self.instance.pk: species = Species() else: diff --git a/geotrek/sensitivity/helpers_sync.py b/geotrek/sensitivity/helpers_sync.py deleted file mode 100644 index 0b0e769cdf..0000000000 --- a/geotrek/sensitivity/helpers_sync.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -from geotrek.sensitivity import views, models - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_geojson(lang, views.SensitiveAreaAPIViewSet, 'sensitiveareas.geojson', - params={'practices': 'Terrestre'}) - for area in models.SensitiveArea.objects.existing().filter(published=True): - name = os.path.join('api', lang, 'sensitiveareas', '{obj.pk}.kml'.format(obj=area)) - self.global_sync.sync_view(lang, views.SensitiveAreaKMLDetail.as_view(), name, pk=area.pk) - self.global_sync.sync_media_file(lang, area.species.pictogram) diff --git a/geotrek/sensitivity/locale/de/LC_MESSAGES/django.po b/geotrek/sensitivity/locale/de/LC_MESSAGES/django.po index 86555e593b..6ae7cbc9af 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 86555e593b..6ae7cbc9af 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 86555e593b..6ae7cbc9af 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 a241bee76c..7c3dd1c9c1 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 86555e593b..6ae7cbc9af 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/mixins.py b/geotrek/sensitivity/mixins.py deleted file mode 100644 index 926d3cb650..0000000000 --- a/geotrek/sensitivity/mixins.py +++ /dev/null @@ -1,57 +0,0 @@ -import logging - -from django.conf import settings -from django.contrib.gis.db.models.functions import Transform -from django.db.models import F, Case, When, Prefetch -from geotrek.common.models import Attachment -from geotrek.common.functions import GeometryType, Buffer, Area - -logger = logging.getLogger(__name__) - - -class SensitiveAreaQueryset: - """Mixin used for to properly querying SensitiveAreas""" - - def get_queryset(self, *args, **kwargs): - qs = super().get_queryset() - qs = ( - ( - qs - .filter(published=True) - .select_related("species", "structure") - .prefetch_related( - "species__practices", - Prefetch( - "attachments", - queryset=Attachment.objects.select_related( - "license", "filetype", "filetype__structure" - ), - ), - ) - ) - .annotate(geom_type=GeometryType(F("geom"))) - .annotate( - geom2d_transformed=Case( - When( - geom_type="POINT", - then=Transform( - Buffer(F("geom"), F("species__radius"), 4), - settings.API_SRID, - ), - ), - When( - geom_type__in=("POLYGON", "MULTIPOLYGON"), - then=Transform(F("geom"), settings.API_SRID), - ), - ) - ) - .annotate(area=Area("geom2d_transformed")) - .order_by("-area") - ) - - if "practices" in self.request.GET: - qs = qs.filter( - species__practices__name__in=self.request.GET["practices"].split(",") - ) - - return qs diff --git a/geotrek/sensitivity/models.py b/geotrek/sensitivity/models.py index de1b83450c..9b8ad7e0ac 100644 --- a/geotrek/sensitivity/models.py +++ b/geotrek/sensitivity/models.py @@ -1,7 +1,3 @@ -""" - Sensitivity models -""" - import datetime import simplekml import logging diff --git a/geotrek/sensitivity/serializers.py b/geotrek/sensitivity/serializers.py index 360f592040..21925296d5 100644 --- a/geotrek/sensitivity/serializers.py +++ b/geotrek/sensitivity/serializers.py @@ -1,41 +1,10 @@ -from django.urls import reverse -from django.utils.translation import get_language from drf_dynamic_fields import DynamicFieldsMixin -from geotrek.api.v2.serializers import AttachmentSerializer from mapentity.serializers import MapentityGeojsonModelSerializer from rest_framework import serializers as rest_serializers -from rest_framework_gis import fields as rest_gis_fields -from rest_framework_gis.serializers import GeoFeatureModelSerializer -from geotrek.common.serializers import PictogramSerializerMixin, TranslatedModelSerializer from . import models as sensitivity_models -class RuleSerializer(PictogramSerializerMixin, rest_serializers.ModelSerializer): - - class Meta: - model = sensitivity_models.Rule - fields = ('id', 'code', 'name', 'pictogram', 'description', 'url') - - -class SportPracticeSerializer(TranslatedModelSerializer): - class Meta: - model = sensitivity_models.SportPractice - fields = ('id', 'name') - - -class SpeciesSerializer(TranslatedModelSerializer, PictogramSerializerMixin): - practices = SportPracticeSerializer(many=True) - period = rest_serializers.SerializerMethodField() - - def get_period(self, obj): - return [getattr(obj, 'period{:02}'.format(p)) for p in range(1, 13)] - - class Meta: - model = sensitivity_models.Species - fields = ['id', 'name', 'practices', 'url', 'pictogram', 'period'] - - class SensitiveAreaSerializer(DynamicFieldsMixin, rest_serializers.ModelSerializer): category = rest_serializers.CharField(source='category_display') structure = rest_serializers.SlugRelatedField('name', read_only=True) @@ -52,33 +21,3 @@ class SensitiveAreaGeojsonSerializer(MapentityGeojsonModelSerializer): class Meta(MapentityGeojsonModelSerializer.Meta): model = sensitivity_models.SensitiveArea fields = ['id', 'species', 'radius', 'published'] - - -class SensitiveAreaAPISerializer(TranslatedModelSerializer): - species = SpeciesSerializer() - kml_url = rest_serializers.SerializerMethodField() - openair_url = rest_serializers.SerializerMethodField() - attachments = AttachmentSerializer(many=True) - rules = RuleSerializer(many=True) - - def get_kml_url(self, obj): - return reverse('sensitivity:sensitivearea_kml_detail', kwargs={'lang': get_language(), 'pk': obj.pk}) - - def get_openair_url(self, obj): - return reverse('sensitivity:sensitivearea_openair_detail', kwargs={'lang': get_language(), 'pk': obj.pk}) - - class Meta: - model = sensitivity_models.SensitiveArea - fields = ( - 'id', 'species', 'description', 'contact', 'published', 'publication_date', - 'kml_url', 'openair_url', 'attachments', 'rules' - ) - - -class SensitiveAreaAPIGeojsonSerializer(GeoFeatureModelSerializer, SensitiveAreaAPISerializer): - # Annotated geom field with API_SRID - geom2d_transformed = rest_gis_fields.GeometryField(read_only=True, precision=7) - - class Meta(SensitiveAreaAPISerializer.Meta): - geo_field = 'geom2d_transformed' - fields = SensitiveAreaAPISerializer.Meta.fields + ('geom2d_transformed', ) diff --git a/geotrek/sensitivity/tests/factories.py b/geotrek/sensitivity/tests/factories.py index 22c626fd6c..542f83b540 100644 --- a/geotrek/sensitivity/tests/factories.py +++ b/geotrek/sensitivity/tests/factories.py @@ -11,12 +11,15 @@ class RuleFactory(factory.django.DjangoModelFactory): + name = factory.Sequence(lambda n: 'Rule %s' % n) + code = factory.Sequence(lambda n: 'CODE%s' % n) + url = factory.faker.Faker('url') + pictogram = factory.django.ImageField() + class Meta: model = models.Rule django_get_or_create = ('code', 'name', 'url', 'pictogram') - name = "Rule" - class SportPracticeFactory(factory.django.DjangoModelFactory): class Meta: diff --git a/geotrek/sensitivity/tests/test_filters.py b/geotrek/sensitivity/tests/test_filters.py new file mode 100644 index 0000000000..40978bef5c --- /dev/null +++ b/geotrek/sensitivity/tests/test_filters.py @@ -0,0 +1,34 @@ +from django.test import TestCase + +from geotrek.sensitivity.filters import SensitiveAreaFilterSet + +from .factories import SensitiveAreaFactory + + +class SensitiveAreaFilterTest(TestCase): + factory = SensitiveAreaFactory + filterset = SensitiveAreaFilterSet + + def test_provider_filter_without_provider(self): + filter_set = SensitiveAreaFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + sensitive_area1 = SensitiveAreaFactory.create(provider="my_provider1") + sensitive_area2 = SensitiveAreaFactory.create(provider="my_provider2") + + filter_set = SensitiveAreaFilterSet() + filter_form = filter_set.form + + self.assertIn( + '', filter_form.as_p() + ) + self.assertIn( + '', filter_form.as_p() + ) + + self.assertIn(sensitive_area1, filter_set.qs) + self.assertIn(sensitive_area2, filter_set.qs) diff --git a/geotrek/sensitivity/tests/test_forms.py b/geotrek/sensitivity/tests/test_forms.py new file mode 100644 index 0000000000..64bf9cd536 --- /dev/null +++ b/geotrek/sensitivity/tests/test_forms.py @@ -0,0 +1,136 @@ +from django.test import TestCase +from mapentity.tests import SuperUserFactory + +from ...authent.models import default_structure +from ..forms import RegulatorySensitiveAreaForm +from .factories import RegulatorySensitiveAreaFactory, SportPracticeFactory + + +class RegulatorySensitiveAreaFormTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.sensitive_area = RegulatorySensitiveAreaFactory() + cls.sport_practice = SportPracticeFactory() + cls.user = SuperUserFactory() + cls.default_structure = default_structure() + + def test_form_valid_with_correct_data(self): + form_data = { + "name": "Test Area", + "elevation": 100, + "pictogram": None, + "practices": [self.sport_practice.id], + "url": "http://test.com", + "period01": True, + "period02": False, + "period03": True, + "period04": False, + "period05": True, + "period06": False, + "period07": True, + "period08": False, + "period09": True, + "period10": False, + "period11": True, + "period12": False, + "instance": self.sensitive_area, + "structure": self.default_structure.pk, + "geom": "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", + } + form = RegulatorySensitiveAreaForm(data=form_data, user=self.user) + self.assertTrue(form.is_valid(), form.errors) + + def test_form_invalid_without_name(self): + form_data = { + "name": "", + "elevation": 100, + "pictogram": None, + "practices": [self.sport_practice.id], + "url": "http://test.com", + "period01": True, + "period02": False, + "period03": True, + "period04": False, + "period05": True, + "period06": False, + "period07": True, + "period08": False, + "period09": True, + "period10": False, + "period11": True, + "period12": False, + "instance": self.sensitive_area, + "structure": self.default_structure.pk, + "geom": "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", + } + form = RegulatorySensitiveAreaForm(data=form_data, user=self.user) + self.assertFalse(form.is_valid()) + self.assertIn("name", form.errors) + + def test_form_save_new_instance(self): + form_data = { + "name": "Test Area", + "elevation": 100, + "pictogram": None, + "practices": [self.sport_practice.id], + "url": "http://test.com", + "period01": True, + "period02": False, + "period03": True, + "period04": False, + "period05": True, + "period06": False, + "period07": True, + "period08": False, + "period09": True, + "period10": False, + "period11": True, + "period12": False, + "geom": "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", + "structure": self.default_structure.pk, + } + form = RegulatorySensitiveAreaForm(data=form_data, user=self.user) + self.assertTrue(form.is_valid(), form.errors) + area = form.save() + self.assertEqual(area.species.name, form_data["name"]) + self.assertEqual(area.species.radius, form_data["elevation"]) + self.assertEqual(area.species.url, form_data["url"]) + self.assertEqual(area.species.practices.count(), len(form_data["practices"])) + for p in range(1, 13): + fieldname = "period{:02}".format(p) + self.assertEqual(getattr(area.species, fieldname), form_data[fieldname]) + + def test_form_save_existing_instance(self): + form_data = { + "name": "Test Area", + "elevation": 100, + "pictogram": None, + "practices": [self.sport_practice.id], + "url": "http://test.com", + "period01": True, + "period02": False, + "period03": True, + "period04": False, + "period05": True, + "period06": False, + "period07": True, + "period08": False, + "period09": True, + "period10": False, + "period11": True, + "period12": False, + "geom": "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", + "structure": self.default_structure.pk, + } + form = RegulatorySensitiveAreaForm( + data=form_data, instance=self.sensitive_area, user=self.user + ) + self.assertTrue(form.is_valid(), form.errors) + area = form.save() + self.assertEqual(area.species.name, form_data["name"]) + self.assertEqual(area.species.radius, form_data["elevation"]) + self.assertEqual(area.species.url, form_data["url"]) + self.assertEqual(area.species.practices.count(), len(form_data["practices"])) + for p in range(1, 13): + fieldname = "period{:02}".format(p) + self.assertEqual(getattr(area.species, fieldname), form_data[fieldname]) diff --git a/geotrek/sensitivity/tests/test_functional.py b/geotrek/sensitivity/tests/test_functional.py index 776d5abe99..dfbf6cab55 100644 --- a/geotrek/sensitivity/tests/test_functional.py +++ b/geotrek/sensitivity/tests/test_functional.py @@ -2,20 +2,22 @@ from django.utils import translation from django.utils.translation import gettext_lazy as _ from django.utils.module_loading import import_string +from rest_framework.reverse import reverse -from geotrek.common.tests import CommonTest, GeotrekAPITestCase -from geotrek.sensitivity.models import SensitiveArea -from geotrek.sensitivity.tests.factories import (SensitiveAreaFactory, SpeciesFactory, SportPracticeFactory, - RegulatorySensitiveAreaFactory, BiodivManagerFactory) +from geotrek.common.tests import CommonTest +from ..forms import RegulatorySensitiveAreaForm +from ..models import SensitiveArea, Species +from .factories import SensitiveAreaFactory, SpeciesFactory, BiodivManagerFactory, RegulatorySensitiveAreaFactory -class SensitiveAreaViewsTests(GeotrekAPITestCase, CommonTest): +class SensitiveAreaViewsTests(CommonTest): model = SensitiveArea modelfactory = SensitiveAreaFactory userfactory = BiodivManagerFactory expected_json_geom = { 'type': 'Polygon', - 'coordinates': [[[3.0, 46.5], [3.0, 46.500027], [3.0000391, 46.500027], [3.0000391, 46.5], [3.0, 46.5]]], + 'coordinates': [[[3.0, 46.5], [3.0, 46.500027], [3.0000391, 46.500027], + [3.0000391, 46.5], [3.0, 46.5]]], } extra_column_list = ['description', 'contact'] @@ -25,43 +27,9 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - - return { - 'attachments': [], - 'contact': 'toto@tata.com', - 'description': 'Blabla', - 'kml_url': '/api/en/sensitiveareas/{}.kml'.format(self.obj.pk), - 'openair_url': '/api/en/sensitiveareas/{}/openair'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'rules': [ - {'code': 'R1', - 'description': '', - 'id': self.obj.rules.all()[0].pk, - 'name': 'Rule1', - 'pictogram': '/media/picto_rule1.png', - 'url': 'http://url.com'}, - {'code': 'R2', - 'description': 'abcdefgh', - 'id': self.obj.rules.all()[1].pk, - 'name': 'Rule2', - 'pictogram': '/media/picto_rule2.png', - 'url': 'http://url.com'}], - 'species': { - 'id': self.obj.species.pk, - 'name': "Species", - 'period': [False, False, False, False, False, True, True, False, False, False, False, False], - 'pictogram': "/media/upload/dummy_img.png", - 'practices': [ - {'id': self.obj.species.practices.all()[0].pk, 'name': "Practice1"}, - {'id': self.obj.species.practices.all()[1].pk, 'name': "Practice2"}, - ], - 'url': self.obj.species.url, - }, + 'published': self.obj.published, + 'radius': self.obj.radius, + 'species': self.obj.species_id, } def get_expected_datatables_attrs(self): @@ -103,140 +71,20 @@ def test_custom_columns_mixin_on_export(self): self.assertEqual(import_string(f'geotrek.{self.model._meta.app_label}.views.{self.model.__name__}FormatList')().columns, ['id', 'description', 'contact']) + def test_regulatory_form_creation(self): + """Test if RegulatorySensitiveAreaForm is used with ?category query parameter""" + response = self.client.get(self._get_add_url(), {'category': Species.REGULATORY}) + self.assertTrue(isinstance(response.context['form'], RegulatorySensitiveAreaForm)) -class RegulatorySensitiveAreaViewsTests(GeotrekAPITestCase, CommonTest): - model = SensitiveArea - modelfactory = RegulatorySensitiveAreaFactory - userfactory = BiodivManagerFactory - expected_json_geom = { - 'type': 'Polygon', - 'coordinates': [[[3.0, 46.5], [3.0, 46.500027], [3.0000391, 46.500027], [3.0000391, 46.5], [3.0, 46.5]]], - } - extra_column_list = ['description', 'contact'] - - def get_expected_geojson_geom(self): - return self.expected_json_geom - - def get_expected_geojson_attrs(self): - return { - 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'attachments': [], - 'contact': 'toto@tata.com', - 'description': 'Blabla', - 'kml_url': '/api/en/sensitiveareas/{}.kml'.format(self.obj.pk), - 'openair_url': '/api/en/sensitiveareas/{}/openair'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'rules': [ - {'code': 'R1', - 'description': '', - 'id': self.obj.rules.all()[0].pk, - 'name': 'Rule1', - 'pictogram': '/media/picto_rule1.png', - 'url': 'http://url.com'}, - {'code': 'R2', - 'description': 'abcdefgh', - 'id': self.obj.rules.all()[1].pk, - 'name': 'Rule2', - 'pictogram': '/media/picto_rule2.png', - 'url': 'http://url.com'}], - 'species': { - 'id': self.obj.species.pk, - 'name': "Species", - 'period': [False, False, False, False, False, True, True, False, False, False, False, False], - 'pictogram': "/media/upload/dummy_img.png", - 'practices': [ - {'id': self.obj.species.practices.all()[0].pk, 'name': "Practice1"}, - {'id': self.obj.species.practices.all()[1].pk, 'name': "Practice2"}, - ], - 'url': self.obj.species.url, - }, - } - - def get_expected_datatables_attrs(self): - return { - 'category': self.obj.category_display, - 'id': self.obj.pk, - 'species': self.obj.species_display, - } - - def setUp(self): - translation.deactivate() - super().setUp() - - def get_bad_data(self): - return { - 'geom': 'doh!' - }, _('Invalid geometry value.') - - def get_good_data(self): - return { - 'name': 'Test', - 'practices': [SportPracticeFactory.create().pk], - 'geom': '{"type": "Polygon", "coordinates":[[[0, 0], [0, 1], [1, 0], [0, 0]]]}', - 'structure': str(self.user.profile.structure.pk), - } - - def _get_add_url(self): - return self.model.get_add_url() + '?category=2' - - def test_crud_status(self): - if self.model is None: - return # Abstract test should not run - - obj = self.modelfactory() - - response = self.client.get(obj.get_list_url()) - self.assertEqual(response.status_code, 200) - - response = self.client.get(obj.get_detail_url().replace(str(obj.pk), '1234567890')) - self.assertEqual(response.status_code, 404) - - response = self.client.get(obj.get_detail_url()) - self.assertEqual(response.status_code, 200) - + def test_regulatory_form_update(self): + """Test if RegulatorySensitiveAreaForm is used with regulatory specie in update view""" + obj = RegulatorySensitiveAreaFactory() response = self.client.get(obj.get_update_url()) - self.assertEqual(response.status_code, 200) + self.assertTrue(isinstance(response.context['form'], RegulatorySensitiveAreaForm)) - self._post_update_form(obj) - - response = self.client.get(obj.get_delete_url()) + def test_kml_detail_view(self): + sa = SensitiveAreaFactory() + response = self.client.get(reverse('sensitivity:sensitivearea_kml_detail', + kwargs={'lang': 'en', 'pk': sa.pk})) self.assertEqual(response.status_code, 200) - - url = obj.get_detail_url() - obj.delete() - response = self.client.get(url) - self.assertEqual(response.status_code, 404) - - self._post_add_form() - - # Test to update without login - self.logout() - - obj = self.modelfactory() - - response = self.client.get(self.model.get_add_url() + '?category=2') - self.assertEqual(response.status_code, 302) - response = self.client.get(obj.get_update_url()) - self.assertEqual(response.status_code, 302) - - def test_custom_columns_mixin_on_list(self): - # Assert columns equal mandatory columns plus custom extra columns - if self.model is None: - return - with override_settings(COLUMNS_LISTS={'sensitivity_view': self.extra_column_list}): - self.assertEqual(import_string(f'geotrek.{self.model._meta.app_label}.views.{self.model.__name__}List')().columns, - ['id', 'species', 'description', 'contact']) - - def test_custom_columns_mixin_on_export(self): - # Assert columns equal mandatory columns plus custom extra columns - if self.model is None: - return - with override_settings(COLUMNS_LISTS={'sensitivity_export': self.extra_column_list}): - self.assertEqual(import_string(f'geotrek.{self.model._meta.app_label}.views.{self.model.__name__}FormatList')().columns, - ['id', 'description', 'contact']) + self.assertEqual(response['content-type'], 'application/vnd.google-earth.kml+xml') diff --git a/geotrek/sensitivity/tests/test_helpers_sync.py b/geotrek/sensitivity/tests/test_helpers_sync.py deleted file mode 100644 index 44fafc1888..0000000000 --- a/geotrek/sensitivity/tests/test_helpers_sync.py +++ /dev/null @@ -1,26 +0,0 @@ -from io import StringIO -import os -from unittest.mock import patch - -from django.conf import settings -from django.test import TestCase - -from geotrek.common.tests.factories import FakeSyncCommand -from geotrek.sensitivity.tests.factories import SensitiveAreaFactory -from geotrek.sensitivity.helpers_sync import SyncRando - - -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.area = SensitiveAreaFactory.create(published=True) - - @patch('sys.stdout', new_callable=StringIO) - def test_sensitivity(self, mock_stdout): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'sensitiveareas.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'sensitiveareas', - '{obj.pk}.kml'.format(obj=self.area)))) diff --git a/geotrek/sensitivity/tests/test_models.py b/geotrek/sensitivity/tests/test_models.py index 3a7ad7ef78..01765aa966 100644 --- a/geotrek/sensitivity/tests/test_models.py +++ b/geotrek/sensitivity/tests/test_models.py @@ -1,25 +1,17 @@ from freezegun import freeze_time -from difflib import SequenceMatcher from django.test import TestCase from django.test.utils import override_settings from django.conf import settings -from geotrek.sensitivity.tests.factories import SensitiveAreaFactory, SpeciesFactory from geotrek.trekking.tests.factories import TrekFactory +from .factories import SensitiveAreaFactory, SpeciesFactory, RuleFactory -def similar_string(a: str, b: str) -> float: - """compare two strings and return similarity ratio - - Args: - a (str): first value - b (str): second_value - - Returns: - float: Similarity ration - """ - return SequenceMatcher(None, a, b).ratio() +class RuleTesCase(TestCase): + def test_str(self): + rule = RuleFactory() + self.assertEqual(str(rule), rule.name) class SensitiveAreaModelTest(TestCase): diff --git a/geotrek/sensitivity/tests/test_views.py b/geotrek/sensitivity/tests/test_views.py index e4a7cad644..f2ccc97019 100644 --- a/geotrek/sensitivity/tests/test_views.py +++ b/geotrek/sensitivity/tests/test_views.py @@ -1,22 +1,18 @@ from freezegun import freeze_time import datetime -from django.contrib.auth.models import User, Permission +from django.contrib.auth.models import Permission from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse +from mapentity.tests import SuperUserFactory from geotrek.authent.tests.factories import StructureFactory, UserProfileFactory from geotrek.authent.tests.base import AuthentFixturesTest -from geotrek.trekking.tests.base import TrekkingManagerTest -from geotrek.common.tests import TranslationResetMixin from geotrek.sensitivity.tests.factories import ( SpeciesFactory, RegulatorySensitiveAreaFactory, SensitiveAreaFactory, - MultiPolygonSensitiveAreaFactory ) -from geotrek.sensitivity.models import SportPractice -from geotrek.sensitivity.filters import SensitiveAreaFilterSet class SensitiveAreaViewsSameStructureTests(AuthentFixturesTest): @@ -66,16 +62,10 @@ class SensitiveAreaTemplatesTest(TestCase): @classmethod def setUpTestData(cls): cls.area = SensitiveAreaFactory.create() + cls.user = SuperUserFactory() def setUp(self): - self.login() - - def login(self): - user = User.objects.create_superuser('splash', 'splash@toto.com', password='booh') - self.client.force_login(user=user) - - def tearDown(self): - self.client.logout() + self.client.force_login(user=self.user) def test_species_name_shown_in_detail_page(self): url = reverse("sensitivity:sensitivearea_detail", kwargs={"pk": self.area.pk}) @@ -83,229 +73,6 @@ def test_species_name_shown_in_detail_page(self): self.assertContains(response, self.area.species.name) -@freeze_time("2020-01-01") -class APIv2Test(TranslationResetMixin, TrekkingManagerTest): - maxDiff = None - - def setUp(self): - super().setUp() - self.sensitivearea = SensitiveAreaFactory.create() - self.species = self.sensitivearea.species - self.pk = self.sensitivearea.pk - self.expected_properties = { - 'create_datetime': self.sensitivearea.date_insert.isoformat().replace('+00:00', 'Z'), - 'update_datetime': self.sensitivearea.date_update.isoformat().replace('+00:00', 'Z'), - 'description': "Blabla", - "elevation": None, - 'attachments': [], - 'contact': 'toto@tata.com', - 'kml_url': 'http://testserver/api/en/sensitiveareas/{pk}.kml'.format(pk=self.pk), - 'openair_url': 'http://testserver/api/en/sensitiveareas/{pk}/openair'.format(pk=self.pk), - 'info_url': self.species.url, - 'species_id': self.species.id, - "name": self.species.name, - "period": [False, False, False, False, False, True, True, False, False, False, False, False], - 'practices': [practice.pk for practice in self.species.practices.all()], - 'rules': [ - {'code': 'R1', - 'description': None, - 'id': self.sensitivearea.rules.all()[0].pk, - 'name': 'Rule1', - 'pictogram': 'http://testserver/media/picto_rule1.png', - 'url': 'http://url.com'}, - {'code': 'R2', - 'description': 'abcdefgh', - 'id': self.sensitivearea.rules.all()[1].pk, - 'name': 'Rule2', - 'pictogram': 'http://testserver/media/picto_rule2.png', - 'url': 'http://url.com'}], - 'provider': '', - 'structure': 'My structure', - 'published': True, - } - self.expected_geom = { - 'type': 'Polygon', - 'coordinates': [[ - [3.0, 46.5], - [3.0, 46.500027], - [3.0000391, 46.500027], - [3.0000391, 46.5], - [3.0, 46.5], - ]], - } - self.expected_result = dict(self.expected_properties) - self.expected_result['id'] = self.pk - self.expected_result['geometry'] = self.expected_geom - self.expected_result['url'] = 'http://testserver/api/v2/sensitivearea/{}/?format=json'.format(self.pk) - self.expected_geo_result = { - 'bbox': [3.0, 46.5, 3.0000391, 46.500027], - 'geometry': self.expected_geom, - 'type': 'Feature', - 'id': self.pk, - 'properties': dict(self.expected_properties), - } - self.expected_geo_result['properties']['url'] = 'http://testserver/api/v2/sensitivearea/{}/?format=geojson'.format(self.pk) - - @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) - def test_detail_sensitivearea(self): - url = reverse('apiv2:sensitivearea-detail', args=(self.pk,)) - params = {'format': 'json', 'period': 'ignore', 'language': 'en'} - response = self.client.get(url, params) - self.assertJSONEqual(response.content.decode(), self.expected_result) - - @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) - def test_detail_sensitivearea_regulatory(self): - self.sensitivearea = RegulatorySensitiveAreaFactory.create(species__period01=True) - url = reverse('apiv2:sensitivearea-detail', args=(self.sensitivearea.pk,)) - params = {'format': 'json', 'period': 'ignore', 'language': 'en'} - response = self.client.get(url, params) - self.assertIsNone(response.json()['species_id']) - - @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) - def test_list_sensitivearea(self): - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'period': 'ignore', 'language': 'en'} - response = self.client.get(url, params) - self.assertJSONEqual(response.content.decode(), { - 'count': 1, - 'previous': None, - 'next': None, - 'results': [self.expected_result], - }) - - @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) - def test_geo_detail_sensitivearea(self): - url = reverse('apiv2:sensitivearea-detail', args=(self.pk,)) - params = {'format': 'geojson', 'period': 'ignore', 'language': 'en'} - response = self.client.get(url, params) - self.assertJSONEqual(response.content.decode(), self.expected_geo_result) - - @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) - def test_geo_list_sensitivearea(self): - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'geojson', 'period': 'ignore', 'language': 'en'} - response = self.client.get(url, params) - self.assertJSONEqual(response.content.decode(), { - 'count': 1, - 'next': None, - 'previous': None, - 'type': 'FeatureCollection', - 'features': [self.expected_geo_result] - }) - - def test_no_duplicates_sensitivearea(self): - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'geojson', 'period': 'ignore', 'language': 'en'} - params['practices'] = ','.join([str(p.pk) for p in self.species.practices.all()]) - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 1, response.json()) - - def test_multipolygon(self): - sensitivearea = MultiPolygonSensitiveAreaFactory.create() - expected_geom = { - 'type': 'MultiPolygon', - 'coordinates': [ - [[ - [3.0, 46.5], - [3.0, 46.500027], - [3.0000391, 46.500027], - [3.0000391, 46.5], - [3.0, 46.5], - ]], - [[ - [3.0001304, 46.50009], - [3.0001304, 46.5001171], - [3.0001695, 46.5001171], - [3.0001695, 46.50009], - [3.0001304, 46.50009], - ]] - ], - } - url = reverse('apiv2:sensitivearea-detail', args=(sensitivearea.pk,)) - params = {'format': 'json', 'period': 'ignore', 'language': 'en'} - response = self.client.get(url, params) - self.assertEqual(response.json()['geometry'], expected_geom) - - @override_settings(SENSITIVITY_OPENAIR_SPORT_PRACTICES=['Practice1', ]) - def test_list_bubble_sensitivearea(self): - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'period': 'ignore', 'language': 'en', 'bubble': 'True'} - response = self.client.get(url, params) - self.expected_result[u'radius'] = None - self.assertJSONEqual(response.content.decode(), { - u'count': 1, - u'previous': None, - u'next': None, - u'results': [self.expected_result], - }) - - def test_list_bubble_sensitivearea_with_point(self): - sensitive_area_point = SensitiveAreaFactory.create(geom='SRID=2154;POINT (700040 6600040)', - species__period01=True, species__radius=5) - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'language': 'en', 'bubble': 'True', 'period': '1'} - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 1) - self.assertEqual(response.json()['results'][0]['radius'], 5) - self.assertEqual(response.json()['results'][0]['name'], sensitive_area_point.species.name) - - def test_list_sportpractice(self): - url = reverse('apiv2:sportpractice-list') - params = {'format': 'json', 'language': 'en'} - response = self.client.get(url, params) - sports_practice = SportPractice.objects.all() - result_sportpractice = [{'id': sp.id, 'name': sp.name} for sp in sports_practice] - self.assertJSONEqual(response.content.decode(), { - u'count': 2, - u'previous': None, - u'next': None, - u'results': result_sportpractice - }) - - def test_filters_structure(self): - other_structure = StructureFactory.create(name='other') - self.sensitivearea_other_structure = SensitiveAreaFactory.create(structure=other_structure) - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'period': 'ignore', 'language': 'en'} - params['structures'] = other_structure.pk - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 1) - self.assertEqual(response.json()['results'][0]['name'], self.sensitivearea_other_structure.species.name) - - def test_filters_no_period(self): - StructureFactory.create() - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'language': 'en'} - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 0) - - def test_filters_any_period(self): - SensitiveAreaFactory.create() - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'period': 'any', 'language': 'en'} - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 2) - - def test_filters_specific_period(self): - sensitive_area_jf = SensitiveAreaFactory.create(species__period01=True, species__period02=True) - SensitiveAreaFactory.create(species__period01=True) - SensitiveAreaFactory.create(species__period04=True) - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'period': '2,3', 'language': 'en'} - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 1) - self.assertEqual(response.json()['results'][0]['name'], sensitive_area_jf.species.name) - - def test_filters_no_period_get_month(self): - sensitive_area_month = SensitiveAreaFactory.create(**{'species__period01': True}) - SensitiveAreaFactory.create(**{'species__period02': True}) - url = reverse('apiv2:sensitivearea-list') - params = {'format': 'json', 'language': 'en'} - response = self.client.get(url, params) - self.assertEqual(response.json()['count'], 1) - self.assertEqual(response.json()['results'][0]['name'], sensitive_area_month.species.name) - - class SensitiveAreaOpenAirViewsTest(TestCase): @classmethod def setUpTestData(cls): @@ -372,28 +139,3 @@ def test_openair_list(self): '\n' 'AC ZSM\n' self.assertContains(response, expected_response) - - -class SensitiveAreaFilterTest(TestCase): - factory = SensitiveAreaFactory - filterset = SensitiveAreaFilterSet - - def test_provider_filter_without_provider(self): - filter_set = SensitiveAreaFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - sensitive_area1 = SensitiveAreaFactory.create(provider='my_provider1') - sensitive_area2 = SensitiveAreaFactory.create(provider='my_provider2') - - filter_set = SensitiveAreaFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(sensitive_area1, filter_set.qs) - self.assertIn(sensitive_area2, filter_set.qs) diff --git a/geotrek/sensitivity/urls.py b/geotrek/sensitivity/urls.py index c8b567049f..988b17e5e4 100644 --- a/geotrek/sensitivity/urls.py +++ b/geotrek/sensitivity/urls.py @@ -1,26 +1,17 @@ -from django.conf import settings from django.urls import path, register_converter -from mapentity.registry import registry -from rest_framework.routers import DefaultRouter +from mapentity.registry import registry, MapEntityOptions -from geotrek.common.urls import PublishableEntityOptions, LangConverter -from . import models -from . import serializers -from . import views +from geotrek.common.urls import LangConverter +from . import models, views -class SensitiveAreaEntityOptions(PublishableEntityOptions): - def get_serializer(self): - return serializers.SensitiveAreaSerializer - - def get_queryset(self): - return self.model.objects.existing() +app_name = 'sensitivity' register_converter(LangConverter, 'lang') -app_name = 'sensitivity' + urlpatterns = [ path('api//sensitiveareas/.kml', views.SensitiveAreaKMLDetail.as_view(), name="sensitivearea_kml_detail"), @@ -30,12 +21,4 @@ def get_queryset(self): views.SensitiveAreaOpenAirList.as_view(), name="sensitivearea_openair_list"), ] -router = DefaultRouter(trailing_slash=False) -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/sensitiveareas', views.SensitiveAreaAPIViewSet, basename='sensitivearea') -urlpatterns += router.urls - -if 'geotrek.trekking' in settings.INSTALLED_APPS: - urlpatterns.append(path('api//treks//sensitiveareas.geojson', - views.TrekSensitiveAreaViewSet.as_view({'get': 'list'}), - name="trek_sensitivearea_geojson")) -urlpatterns += registry.register(models.SensitiveArea, SensitiveAreaEntityOptions) +urlpatterns += registry.register(models.SensitiveArea, MapEntityOptions) diff --git a/geotrek/sensitivity/views.py b/geotrek/sensitivity/views.py index 6c08b03833..44d00b8b26 100644 --- a/geotrek/sensitivity/views.py +++ b/geotrek/sensitivity/views.py @@ -4,34 +4,22 @@ from django.conf import settings from django.contrib.gis.db.models.functions import Transform -from django.db.models import F, Case, When -from django.http import Http404, HttpResponse -from django.shortcuts import get_object_or_404 +from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView from django.views.generic.detail import BaseDetailView from mapentity.views import (MapEntityCreate, MapEntityUpdate, MapEntityList, MapEntityDetail, MapEntityDelete, MapEntityFormat, LastModifiedMixin) -from rest_framework import permissions as rest_permissions, viewsets from geotrek.authent.decorators import same_structure_required -from geotrek.common.functions import GeometryType, Buffer, Area -from geotrek.common.mixins.api import APIViewSet from geotrek.common.mixins.views import CustomColumnsMixin from geotrek.common.permissions import PublicOrReadPermMixin from geotrek.common.viewsets import GeotrekMapentityViewSet from .filters import SensitiveAreaFilterSet from .forms import SensitiveAreaForm, RegulatorySensitiveAreaForm -from .mixins import SensitiveAreaQueryset from .models import SensitiveArea, Species, SportPractice -from .serializers import SensitiveAreaSerializer, SensitiveAreaAPIGeojsonSerializer, SensitiveAreaAPISerializer, \ - SensitiveAreaGeojsonSerializer +from .serializers import SensitiveAreaSerializer, SensitiveAreaGeojsonSerializer -if 'geotrek.trekking' in settings.INSTALLED_APPS: - from geotrek.trekking.models import Trek - -if 'geotrek.diving' in settings.INSTALLED_APPS: - from geotrek.diving.models import Dive logger = logging.getLogger(__name__) @@ -114,68 +102,6 @@ def get_queryset(self): return qs -class SensitiveAreaAPIViewSet(SensitiveAreaQueryset, APIViewSet): - model = SensitiveArea - serializer_class = SensitiveAreaAPISerializer - geojson_serializer_class = SensitiveAreaAPIGeojsonSerializer - queryset = SensitiveArea.objects.existing() - - -if 'geotrek.trekking' in settings.INSTALLED_APPS: - class TrekSensitiveAreaViewSet(viewsets.ModelViewSet): - model = SensitiveArea - serializer_class = SensitiveAreaAPIGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - pk = self.kwargs['pk'] - trek = get_object_or_404(Trek.objects.existing(), pk=pk) - if not trek.is_public(): - raise Http404 - qs = trek.published_sensitive_areas - qs = qs.prefetch_related('species') - qs = qs.annotate(geom_type=GeometryType(F('geom'))) - qs = qs.annotate(geom2d_transformed=Case( - When(geom_type='POINT', then=Transform(Buffer(F('geom'), F('species__radius'), 4), settings.API_SRID)), - When(geom_type='POLYGON', then=Transform(F('geom'), settings.API_SRID)) - )) - # Ensure smaller areas are at the end of the list, ie above bigger areas on the map - # to ensure we can select every area in case of overlapping - qs = qs.annotate(area=Area('geom2d_transformed')).order_by('-area') - - if 'practices' in self.request.GET: - qs = qs.filter(species__practices__name__in=self.request.GET['practices'].split(',')) - - return qs - -if 'geotrek.diving' in settings.INSTALLED_APPS: - class DiveSensitiveAreaViewSet(viewsets.ModelViewSet): - model = SensitiveArea - serializer_class = SensitiveAreaAPIGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - pk = self.kwargs['pk'] - dive = get_object_or_404(Dive.objects.existing(), pk=pk) - if not dive.is_public: - raise Http404 - qs = dive.published_sensitive_areas - qs = qs.prefetch_related('species') - qs = qs.annotate(geom_type=GeometryType(F('geom'))) - qs = qs.annotate(geom2d_transformed=Case( - When(geom_type='POINT', then=Transform(Buffer(F('geom'), F('species__radius'), 4), settings.API_SRID)), - When(geom_type='POLYGON', then=Transform(F('geom'), settings.API_SRID)) - )) - # Ensure smaller areas are at the end of the list, ie above bigger areas on the map - # to ensure we can select every area in case of overlapping - qs = qs.annotate(area=Area('geom2d_transformed')).order_by('-area') - - if 'practices' in self.request.GET: - qs = qs.filter(species__practices__name__in=self.request.GET['practices'].split(',')) - - return qs - - class SensitiveAreaKMLDetail(LastModifiedMixin, PublicOrReadPermMixin, BaseDetailView): queryset = SensitiveArea.objects.existing() diff --git a/geotrek/settings/base.py b/geotrek/settings/base.py index 851f9ae738..e5c477f530 100644 --- a/geotrek/settings/base.py +++ b/geotrek/settings/base.py @@ -679,9 +679,7 @@ def api_bbox(bbox, buffer): "paste_as_text": True } -SYNC_RANDO_ROOT = os.path.join(VAR_DIR, 'data') SYNC_MOBILE_ROOT = os.path.join(VAR_DIR, 'mobile') -SYNC_RANDO_OPTIONS = {} SYNC_MOBILE_OPTIONS = {'skip_tiles': False} ''' diff --git a/geotrek/settings/env_dev.py b/geotrek/settings/env_dev.py index d7b3e2a5d0..3f230a5466 100644 --- a/geotrek/settings/env_dev.py +++ b/geotrek/settings/env_dev.py @@ -36,11 +36,6 @@ 'debug_toolbar.middleware.DebugToolbarMiddleware', ) - -SYNC_RANDO_OPTIONS = { - 'url': 'http://geotrek.local:8000' # Mandatory for dev mode. Must point to the same domain than SERVER_NAME in .env -} - LOGGING['loggers']['']['level'] = 'DEBUG' CACHES['default']['BACKEND'] = 'django.core.cache.backends.locmem.LocMemCache' diff --git a/geotrek/signage/helpers_sync.py b/geotrek/signage/helpers_sync.py deleted file mode 100644 index 9811937bcb..0000000000 --- a/geotrek/signage/helpers_sync.py +++ /dev/null @@ -1,13 +0,0 @@ -from geotrek.infrastructure import models -from geotrek.signage.views import SignageAPIViewSet - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_geojson(lang, SignageAPIViewSet, 'signages.geojson') - self.global_sync.sync_static_file(lang, 'signage/picto-signage.png') - models_picto = [models.InfrastructureType] - self.global_sync.sync_pictograms(lang, models_picto, zipfile=self.global_sync.zipfile) diff --git a/geotrek/signage/locale/de/LC_MESSAGES/django.po b/geotrek/signage/locale/de/LC_MESSAGES/django.po index 8f9efa87e9..cca4395b18 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 8f9efa87e9..cca4395b18 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 8f9efa87e9..cca4395b18 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 c150aa1119..223306343b 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: 2024-02-09 17:51+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 8f9efa87e9..cca4395b18 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 8f9efa87e9..cca4395b18 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/models.py b/geotrek/signage/models.py index ecb34a358b..e2aa7c2e10 100755 --- a/geotrek/signage/models.py +++ b/geotrek/signage/models.py @@ -137,7 +137,7 @@ def tourism_signages(cls, tourism_obj, queryset=None): @property def order_blades(self): - return self.blade_set.all().order_by(collate_c('number')) + return self.blade_set.existing().order_by(collate_c('number')) @property def coordinates(self): diff --git a/geotrek/signage/serializers.py b/geotrek/signage/serializers.py index 1c854c35af..6d155d508e 100644 --- a/geotrek/signage/serializers.py +++ b/geotrek/signage/serializers.py @@ -89,30 +89,6 @@ class Meta(MapentityGeojsonModelSerializer.Meta): fields = ('id', 'number') -class BladeAPISerializer(serializers.ModelSerializer): - type = BladeTypeSerializer() - structure = StructureSerializer() - order_lines = serializers.SerializerMethodField() - - def get_order_lines(self, obj): - return obj.order_lines.values_list('pk', flat=True) - - class Meta: - model = signage_models.Blade - id_field = 'id' # By default on this model it's topo_object = OneToOneField(parent_link=True) - fields = ('id', 'structure', 'number', 'order_lines', 'type', 'color', 'conditions', 'direction') - # TODO: Do a lineserializer for order_lines - - -class BladeAPIGeojsonSerializer(GeoFeatureModelSerializer, BladeAPISerializer): - # Annotated geom field with API_SRID - api_geom = rest_gis_fields.GeometryField(read_only=True, precision=7) - - class Meta(BladeAPISerializer.Meta): - geo_field = 'api_geom' - fields = BladeAPISerializer.Meta.fields + ('api_geom', ) - - class CSVBladeSerializer(CSVSerializer): def serialize(self, queryset, **options): """ diff --git a/geotrek/signage/tests/factories.py b/geotrek/signage/tests/factories.py index ca0f4f6b86..26950cc3b8 100644 --- a/geotrek/signage/tests/factories.py +++ b/geotrek/signage/tests/factories.py @@ -102,7 +102,7 @@ class BladeFactory(factory.django.DjangoModelFactory): class Meta: model = models.Blade - number = 1 + number = factory.Sequence(lambda n: "%s" % n) type = factory.SubFactory(BladeTypeFactory) direction = factory.SubFactory(BladeDirectionFactory) color = factory.SubFactory(BladeColorFactory) diff --git a/geotrek/signage/tests/test_command_signage.py b/geotrek/signage/tests/test_commands.py similarity index 100% rename from geotrek/signage/tests/test_command_signage.py rename to geotrek/signage/tests/test_commands.py diff --git a/geotrek/signage/tests/test_filters.py b/geotrek/signage/tests/test_filters.py new file mode 100644 index 0000000000..238e4ad408 --- /dev/null +++ b/geotrek/signage/tests/test_filters.py @@ -0,0 +1,114 @@ +from django.test import TestCase + +from geotrek.common.tests.factories import OrganismFactory +from geotrek.maintenance.tests.factories import SignageInterventionFactory + +from ..filters import BladeFilterSet, SignageFilterSet +from .factories import BladeFactory, SignageFactory + + +class SignageFilterTest(TestCase): + filterset = SignageFilterSet + + def test_none_implantation_year_filter(self): + SignageFactory.create() + form = self.filterset().form.as_p() + self.assertNotIn('option value="" selected>None2015', form) + self.assertIn('', form) + filter = self.filterset(data={"implantation_year": [2015]}) + self.assertTrue(i in filter.qs) + self.assertFalse(i2 in filter.qs) + + def test_implantation_year_filter_with_str(self): + i = SignageFactory.create(implantation_year=2015) + i2 = SignageFactory.create(implantation_year=2016) + filter_set = self.filterset(data={"implantation_year": "toto"}) + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(i, filter_set.qs) + self.assertIn(i2, filter_set.qs) + + def test_form_should_have_signage_intervention_year_choices(self): + SignageInterventionFactory(begin_date="2015-01-01") + SignageInterventionFactory(begin_date="2020-01-01") + filter_set = self.filterset() + choice_values = [ + choice[0] for choice in filter_set.form.fields["intervention_year"].choices + ] + self.assertIn(2015, choice_values) + self.assertIn(2020, choice_values) + self.assertNotIn(2022, choice_values) + + def test_filter_by_intervention_year(self): + filtered_signage_intervention = SignageInterventionFactory( + begin_date="2015-01-01" + ) + non_filtered_signage_intervention = SignageInterventionFactory( + begin_date="2020-01-01" + ) + filter_set = self.filterset(data={"intervention_year": [2015]}) + qs = filter_set.qs + + self.assertEqual(1, len(qs)) + self.assertIn(filtered_signage_intervention.target, qs) + self.assertNotIn(non_filtered_signage_intervention.target, qs) + + def test_provider_filter_without_provider(self): + filter_set = self.filterset(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + signage1 = SignageFactory.create(provider="my_provider1") + signage2 = SignageFactory.create(provider="my_provider2") + + filter_set = self.filterset() + filter_form = filter_set.form + + self.assertIn( + '', filter_form.as_p() + ) + self.assertIn( + '', filter_form.as_p() + ) + + self.assertIn(signage1, filter_set.qs) + self.assertIn(signage2, filter_set.qs) + + +class BladeFilterSetTest(TestCase): + factory = BladeFactory + filterset = BladeFilterSet + + @classmethod + def setUpTestData(cls): + cls.manager = OrganismFactory() + cls.manager2 = OrganismFactory() + + cls.signage = SignageFactory(manager=cls.manager, code="COUCOU") + cls.signage2 = SignageFactory(manager=cls.manager2, code="ADIEU") + + cls.blade = cls.factory(signage=cls.signage) + cls.blade2 = cls.factory(signage=cls.signage2) + + def test_filter_by_organism(self): + filter = BladeFilterSet( + data={ + "manager": [ + self.manager.pk, + ] + } + ) + self.assertIn(self.blade, filter.qs) + self.assertNotIn(self.blade2, filter.qs) diff --git a/geotrek/signage/tests/test_helpers_sync.py b/geotrek/signage/tests/test_helpers_sync.py deleted file mode 100644 index e504737aa1..0000000000 --- a/geotrek/signage/tests/test_helpers_sync.py +++ /dev/null @@ -1,25 +0,0 @@ -from io import StringIO -import os -from unittest.mock import patch - -from django.conf import settings -from django.test import TestCase - -from geotrek.common.tests.factories import FakeSyncCommand -from geotrek.signage.tests.factories import SignageFactory -from geotrek.signage.helpers_sync import SyncRando - - -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.signage = SignageFactory.create(published=True) - - @patch('sys.stdout', new_callable=StringIO) - def test_signage(self, mock_stdout): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'signages.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'static', 'signage', 'picto-signage.png'))) diff --git a/geotrek/signage/tests/test_models.py b/geotrek/signage/tests/test_models.py index a2496995f7..fcb87b9fac 100644 --- a/geotrek/signage/tests/test_models.py +++ b/geotrek/signage/tests/test_models.py @@ -5,7 +5,8 @@ from geotrek.authent.tests.factories import UserFactory, StructureFactory from geotrek.signage.models import Blade -from geotrek.signage.tests.factories import BladeFactory, BladeTypeFactory, LinePictogramFactory, SealingFactory, SignageFactory, SignageConditionFactory, BladeConditionFactory +from geotrek.signage.tests.factories import BladeFactory, BladeTypeFactory, LinePictogramFactory, SealingFactory, \ + SignageFactory, SignageConditionFactory, BladeConditionFactory, SignageTypeNoPictogramFactory from geotrek.infrastructure.tests.factories import InfrastructureFactory @@ -88,3 +89,10 @@ class LinePictogramModelTest(TestCase): def test_str_linepictogram(self): linepictogram = LinePictogramFactory(label="fresnais") self.assertEqual(str(linepictogram), "fresnais") + + +class SignageTypeTestCase(TestCase): + def test_pictogram_url_default(self): + """Return signage type default pictogram if not defined""" + signage_type = SignageTypeNoPictogramFactory() + self.assertEqual(signage_type.get_pictogram_url(), '/static/signage/picto-signage.png') diff --git a/geotrek/signage/tests/test_views.py b/geotrek/signage/tests/test_views.py index 854834932b..7c3a5c8998 100755 --- a/geotrek/signage/tests/test_views.py +++ b/geotrek/signage/tests/test_views.py @@ -1,7 +1,7 @@ -from collections import OrderedDict import csv -from io import StringIO import json +from collections import OrderedDict +from io import StringIO from django.conf import settings from django.contrib.auth.models import Permission, User @@ -9,19 +9,22 @@ from django.test.utils import override_settings from django.utils.translation import gettext -from geotrek.common.tests import CommonTest, GeotrekAPITestCase -from geotrek.authent.tests.base import AuthentFixturesTest from geotrek.authent.tests.factories import PathManagerFactory, StructureFactory -from geotrek.common.tests.factories import OrganismFactory -from geotrek.signage.models import Signage, Blade +from geotrek.common.tests import CommonTest from geotrek.core.tests.factories import PathFactory -from geotrek.signage.tests.factories import (SignageConditionFactory, SignageFactory, SignageTypeFactory, BladeConditionFactory, BladeFactory, BladeTypeFactory, - SignageNoPictogramFactory, BladeDirectionFactory, BladeColorFactory, - LineFactory, LineDirectionFactory) -from geotrek.signage.filters import BladeFilterSet, SignageFilterSet -from geotrek.infrastructure.tests.test_filters import InfraFilterTestMixin - -from mapentity.tests.factories import SuperUserFactory +from geotrek.signage.models import Blade, Signage +from geotrek.signage.tests.factories import ( + BladeColorFactory, + BladeConditionFactory, + BladeDirectionFactory, + BladeFactory, + BladeTypeFactory, + LineDirectionFactory, + LineFactory, + SignageConditionFactory, + SignageFactory, + SignageTypeFactory, +) class SignageTest(TestCase): @@ -72,7 +75,7 @@ def test_direction_field_on_each_line_on_detail_page_when_direction_on_lines_ena self.assertContains(response, "A direction on the line 2") -class BladeViewsTest(GeotrekAPITestCase, CommonTest): +class BladeViewsTest(CommonTest): model = Blade modelfactory = BladeFactory userfactory = PathManagerFactory @@ -87,20 +90,7 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'color': self.obj.color.pk, - 'conditions': list(self.obj.conditions.values_list("pk", flat=True)), - 'direction': self.obj.direction.pk, - 'number': '1', - 'order_lines': [self.obj.lines.get().pk], - 'structure': {'id': self.obj.structure.pk, 'name': 'My structure'}, - 'type': { - 'label': 'Blade type' - } + 'number': self.obj.number, } def get_expected_datatables_attrs(self): @@ -334,7 +324,7 @@ def test_direction_field_visibility_on_detail_page_when_direction_on_lines_enabl self.assertContains(response, "A direction on the line") -class SignageViewsTest(GeotrekAPITestCase, CommonTest): +class SignageViewsTest(CommonTest): model = Signage modelfactory = SignageFactory userfactory = PathManagerFactory @@ -349,31 +339,8 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'code': '', - 'conditions': list(self.obj.conditions.values_list("pk", flat=True)), - 'manager': self.obj.manager.pk, - 'name': 'Signage', - 'printed_elevation': 4807, - 'publication_date': '2020-03-17', + 'name': self.obj.name, 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False} - ], - 'sealing': self.obj.sealing.pk, - 'structure': {'id': self.obj.structure.pk, 'name': 'My structure'}, - 'type': { - 'id': self.obj.type.pk, - 'label': 'Signage type', - 'pictogram': '/media/upload/signage_type.png', - }, } def get_expected_datatables_attrs(self): @@ -415,101 +382,3 @@ def test_check_structure_or_none_related_are_visible(self): form = response.context['form'] type = form.fields['type'] self.assertTrue((signagetype.pk, str(signagetype)) in type.choices) - - def test_no_pictogram(self): - self.obj = SignageNoPictogramFactory.create(publication_date='2020-03-17') - response = self.client.get('/api/en/signages/{}'.format(self.obj.pk)) - self.assertEqual(response.status_code, 200) - expected_json_attrs = {'id': self.obj.pk, **self.get_expected_json_attrs()} - expected_json_attrs['type']['pictogram'] = '/static/signage/picto-signage.png' - self.assertJSONEqual(response.content, expected_json_attrs) - - -class SignageFilterTest(InfraFilterTestMixin, AuthentFixturesTest): - factory = SignageFactory - filterset = SignageFilterSet - - def test_none_implantation_year_filter(self): - - self.login() - model = self.factory._meta.model - SignageFactory.create() - response = self.client.get(model.get_list_url()) - self.assertNotContains(response, 'option value="" selected>None2015') - self.assertContains(response, '') - - filter = SignageFilterSet(data={'implantation_year': [2015]}) - self.assertTrue(i in filter.qs) - self.assertFalse(i2 in filter.qs) - - def test_implantation_year_filter_with_str(self): - i = SignageFactory.create(implantation_year=2015) - i2 = SignageFactory.create(implantation_year=2016) - filter_set = SignageFilterSet(data={'implantation_year': 'toto'}) - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(i, filter_set.qs) - self.assertIn(i2, filter_set.qs) - - def test_provider_filter_without_provider(self): - filter_set = SignageFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - signage1 = SignageFactory.create(provider='my_provider1') - signage2 = SignageFactory.create(provider='my_provider2') - - filter_set = SignageFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(signage1, filter_set.qs) - self.assertIn(signage2, filter_set.qs) - - -class BladeFilterSetTest(TestCase): - factory = BladeFactory - filterset = BladeFilterSet - - @classmethod - def setUpTestData(cls): - cls.model = cls.factory._meta.model - cls.user = SuperUserFactory.create() - - cls.manager = OrganismFactory() - cls.manager2 = OrganismFactory() - - cls.signage = SignageFactory(manager=cls.manager, code="COUCOU") - cls.signage2 = SignageFactory(manager=cls.manager2, code="ADIEU") - - cls.blade = cls.factory(signage=cls.signage) - cls.blade2 = cls.factory(signage=cls.signage2) - - def setUp(self): - self.client.force_login(self.user) - - def test_filter_by_organism(self): - filter = BladeFilterSet(data={'manager': [self.manager.pk,]}) - response = self.client.get(self.model.get_list_url()) - - self.assertEqual(response.status_code, 200) - - self.assertIn(self.blade, filter.qs) - self.assertNotIn(self.blade2, filter.qs) diff --git a/geotrek/signage/urls.py b/geotrek/signage/urls.py index c594d1f5d6..2b6b71bcad 100644 --- a/geotrek/signage/urls.py +++ b/geotrek/signage/urls.py @@ -2,28 +2,20 @@ from django.urls import path, register_converter from mapentity.registry import registry -from rest_framework.routers import DefaultRouter from . import models from geotrek.trekking.views import TrekSignageViewSet from geotrek.common.urls import LangConverter -from .views import SignageAPIViewSet, BladeAPIViewSet register_converter(LangConverter, 'lang') app_name = 'signage' urlpatterns = registry.register(models.Signage, menu=settings.SIGNAGE_MODEL_ENABLED) -router = DefaultRouter(trailing_slash=False) - - -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/signages', SignageAPIViewSet, basename='signage') if settings.BLADE_ENABLED: urlpatterns += registry.register(models.Blade, menu=False) - router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/blades', BladeAPIViewSet, basename='blade') -urlpatterns += router.urls urlpatterns += [ path('api//treks//signages.geojson', diff --git a/geotrek/signage/views.py b/geotrek/signage/views.py index 6ffdfcc5b8..68e5e9420f 100755 --- a/geotrek/signage/views.py +++ b/geotrek/signage/views.py @@ -8,7 +8,6 @@ MapEntityDocument, MapEntityCreate, MapEntityUpdate, MapEntityDelete) from geotrek.authent.decorators import same_structure_required -from geotrek.common.mixins.api import APIViewSet from geotrek.common.mixins.forms import FormsetMixin from geotrek.common.mixins.views import CustomColumnsMixin from geotrek.common.viewsets import GeotrekMapentityViewSet @@ -16,10 +15,8 @@ from .filters import SignageFilterSet, BladeFilterSet from .forms import SignageForm, BladeForm, LineFormset from .models import Signage, Blade -from .serializers import (SignageSerializer, BladeSerializer, - SignageAPIGeojsonSerializer, CSVBladeSerializer, ZipBladeShapeSerializer, - SignageAPISerializer, BladeAPISerializer, BladeAPIGeojsonSerializer, SignageGeojsonSerializer, - BladeGeojsonSerializer) +from .serializers import (SignageSerializer, BladeSerializer, CSVBladeSerializer, ZipBladeShapeSerializer, + SignageGeojsonSerializer, BladeGeojsonSerializer) logger = logging.getLogger(__name__) @@ -99,15 +96,6 @@ def get_queryset(self): return qs -class SignageAPIViewSet(APIViewSet): - model = Signage - serializer_class = SignageAPISerializer - geojson_serializer_class = SignageAPIGeojsonSerializer - - def get_queryset(self): - return Signage.objects.existing().filter(published=True).annotate(api_geom=Transform("geom", settings.API_SRID)) - - class BladeDetail(MapEntityDetail): queryset = Blade.objects.existing() @@ -237,12 +225,3 @@ def get_queryset(self): else: qs = qs.select_related('signage', 'direction', 'type', 'color').prefetch_related('conditions') return qs - - -class BladeAPIViewSet(APIViewSet): - model = Blade - serializer_class = BladeAPISerializer - geojson_serializer_class = BladeAPIGeojsonSerializer - - def get_queryset(self): - return Blade.objects.existing().annotate(api_geom=Transform("signage__geom", settings.API_SRID)) diff --git a/geotrek/tourism/admin.py b/geotrek/tourism/admin.py index b4fbcab5b2..459e6d5548 100644 --- a/geotrek/tourism/admin.py +++ b/geotrek/tourism/admin.py @@ -57,7 +57,7 @@ class TouristicContentType2Inline(TranslationTabularInline): class TouristicContentCategoryAdmin(MergeActionMixin, TabbedTranslationAdmin): - list_display = ('label', 'prefixed_id', 'order', 'pictogram_img', 'type1_label', 'type2_label') + list_display = ('id', 'label', 'order', 'pictogram_img', 'type1_label', 'type2_label') search_fields = ('label',) inlines = [ TouristicContentType1Inline, diff --git a/geotrek/tourism/filters.py b/geotrek/tourism/filters.py index b59c0bb90f..ef9f534791 100644 --- a/geotrek/tourism/filters.py +++ b/geotrek/tourism/filters.py @@ -1,15 +1,19 @@ -from django.utils.translation import gettext_lazy as _ - -from django_filters.filters import ModelMultipleChoiceFilter, ChoiceFilter import django_filters.rest_framework -from django.db.models import Q -from geotrek.authent.filters import StructureRelatedFilterSet from django import forms from django.utils.datetime_safe import datetime +from django.utils.translation import gettext_lazy as _ +from django_filters.filters import ChoiceFilter, ModelMultipleChoiceFilter -from .models import TouristicContent, TouristicEvent, TouristicContentType1, TouristicContentType2 +from geotrek.authent.filters import StructureRelatedFilterSet from geotrek.zoning.filters import ZoningFilterSet +from .models import ( + TouristicContent, + TouristicContentType1, + TouristicContentType2, + TouristicEvent, +) + class TypeField(forms.ModelMultipleChoiceField): def label_from_instance(self, obj): @@ -79,18 +83,3 @@ class Meta(StructureRelatedFilterSet.Meta): 'before', 'approved', 'source', 'portal', 'provider', 'bookable', 'cancelled', 'place' ] - - -class TouristicEventApiFilterSet(django_filters.rest_framework.FilterSet): - ends_after = django_filters.DateFilter(method='events_end_after') - - class Meta: - model = TouristicEvent - fields = ('ends_after', ) - - def events_end_after(self, queryset, name, value): - if not value: - return queryset - return queryset.filter( - Q(end_date__isnull=True) | Q(end_date__gte=value) - ) diff --git a/geotrek/tourism/helpers_sync.py b/geotrek/tourism/helpers_sync.py deleted file mode 100644 index 6fab2777a2..0000000000 --- a/geotrek/tourism/helpers_sync.py +++ /dev/null @@ -1,87 +0,0 @@ -from django.conf import settings -from django.db.models import Q -from django.utils import timezone -import os - -from geotrek.tourism import views as tourism_views -from geotrek.tourism import models - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_geojson(lang, tourism_views.TouristicContentAPIViewSet, 'touristiccontents.geojson') - self.global_sync.sync_geojson(lang, tourism_views.TouristicEventAPIViewSet, 'touristicevents.geojson', - params={'ends_after': timezone.now().strftime('%Y-%m-%d')}) - - # picto touristic events - self.global_sync.sync_file(lang, - os.path.join('tourism', 'touristicevent.svg'), - settings.STATIC_ROOT, - settings.STATIC_URL, - zipfile=self.global_sync.zipfile) - - # json with - params = {} - - if self.global_sync.categories: - params.update({'categories': ','.join(category for category in self.global_sync.categories), }) - - if self.global_sync.with_events: - params.update({'events': '1'}) - - self.global_sync.sync_json(lang, tourism_views.TouristicCategoryView, - 'touristiccategories', - zipfile=self.global_sync.zipfile, params=params) - - # pictos touristic content catgories - for category in models.TouristicContentCategory.objects.all(): - self.global_sync.sync_media_file(lang, category.pictogram, zipfile=self.global_sync.zipfile) - - contents = models.TouristicContent.objects.existing().order_by('pk') - contents = contents.filter(**{'published_{lang}'.format(lang=lang): True}) - - if self.global_sync.source: - contents = contents.filter(source__name__in=self.global_sync.source) - - if self.global_sync.portal: - contents = contents.filter(Q(portal__name=self.global_sync.portal) | Q(portal=None)) - - for content in contents: - self.sync_content(lang, content) - - events = models.TouristicEvent.objects.existing().order_by('pk') - events = events.filter(**{'published_{lang}'.format(lang=lang): True}) - - if self.global_sync.source: - events = events.filter(source__name__in=self.global_sync.source) - - if self.global_sync.portal: - events = events.filter(Q(portal__name=self.global_sync.portal) | Q(portal=None)) - - for event in events: - self.sync_event(lang, event) - - # Information desks - self.global_sync.sync_geojson(lang, tourism_views.InformationDeskViewSet, 'information_desks.geojson') - for pk in models.InformationDeskType.objects.values_list('pk', flat=True): - name = 'information_desks-{}.geojson'.format(pk) - self.global_sync.sync_geojson(lang, tourism_views.InformationDeskViewSet, name, type=pk) - for desk in models.InformationDesk.objects.all(): - self.global_sync.sync_media_file(lang, desk.thumbnail) - - def sync_event(self, lang, event): - self.global_sync.sync_metas(lang, tourism_views.TouristicEventMeta, event) - self.global_sync.sync_pdf(lang, event, - tourism_views.TouristicEventDocumentPublic.as_view(model=type(event))) - for picture, resized in event.resized_pictures: - self.global_sync.sync_media_file(lang, resized) - - def sync_content(self, lang, content): - self.global_sync.sync_metas(lang, tourism_views.TouristicContentMeta, content) - self.global_sync.sync_pdf(lang, content, - tourism_views.TouristicContentDocumentPublic.as_view(model=type(content))) - for picture, resized in content.resized_pictures: - self.global_sync.sync_media_file(lang, resized) diff --git a/geotrek/tourism/locale/de/LC_MESSAGES/django.po b/geotrek/tourism/locale/de/LC_MESSAGES/django.po index f095e4e2bf..ed5a7ace73 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: 2015-10-22 14:33+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -250,9 +250,6 @@ msgstr "Touristische Inhalte" msgid "Touristic contents" msgstr "Touristische Inhalte" -msgid "touristic-content" -msgstr "touristische Inhalte" - msgid "Published touristic contents" msgstr "Veröffentlichte touristische Inhalte" @@ -349,7 +346,7 @@ msgstr "" msgid "Price" msgstr "" -msgid "0 mean free" +msgid "0 means free" msgstr "" msgid "Select a place in the list or locate the event directly on the map" @@ -372,9 +369,6 @@ msgstr "von der {begin}" msgid "from {begin} to {end}" msgstr "von {begin} bis {end}" -msgid "touristic-event" -msgstr "touristische Veranstaltung" - msgid "Display order" msgstr "" diff --git a/geotrek/tourism/locale/en/LC_MESSAGES/django.po b/geotrek/tourism/locale/en/LC_MESSAGES/django.po index bfceadaa45..07a78d0ad0 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -63,7 +63,7 @@ msgid "Start date is after end date" msgstr "" msgid "Label" -msgstr "" +msgstr "Label" msgid "Information desk type" msgstr "" @@ -249,9 +249,6 @@ msgstr "" msgid "Touristic contents" msgstr "" -msgid "touristic-content" -msgstr "" - msgid "Published touristic contents" msgstr "" @@ -348,7 +345,7 @@ msgstr "" msgid "Price" msgstr "" -msgid "0 mean free" +msgid "0 means free" msgstr "" msgid "Select a place in the list or locate the event directly on the map" @@ -371,9 +368,6 @@ msgstr "" msgid "from {begin} to {end}" msgstr "" -msgid "touristic-event" -msgstr "" - msgid "Display order" msgstr "" diff --git a/geotrek/tourism/locale/es/LC_MESSAGES/django.po b/geotrek/tourism/locale/es/LC_MESSAGES/django.po index bfceadaa45..27ee523f6d 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -249,9 +249,6 @@ msgstr "" msgid "Touristic contents" msgstr "" -msgid "touristic-content" -msgstr "" - msgid "Published touristic contents" msgstr "" @@ -348,7 +345,7 @@ msgstr "" msgid "Price" msgstr "" -msgid "0 mean free" +msgid "0 means free" msgstr "" msgid "Select a place in the list or locate the event directly on the map" @@ -371,9 +368,6 @@ msgstr "" msgid "from {begin} to {end}" msgstr "" -msgid "touristic-event" -msgstr "" - msgid "Display order" msgstr "" diff --git a/geotrek/tourism/locale/fr/LC_MESSAGES/django.po b/geotrek/tourism/locale/fr/LC_MESSAGES/django.po index 757a245039..7d19f5cb6a 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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" @@ -249,9 +249,6 @@ msgstr "Contenuto turistico" msgid "Touristic contents" msgstr "Contenuti turistici" -msgid "touristic-content" -msgstr "touristic-content" - msgid "Published touristic contents" msgstr "Contenuti turistici pubblicati" @@ -348,7 +345,7 @@ msgstr "" msgid "Price" msgstr "" -msgid "0 mean free" +msgid "0 means free" msgstr "" msgid "Select a place in the list or locate the event directly on the map" @@ -371,9 +368,6 @@ msgstr "a partire da {begin}" msgid "from {begin} to {end}" msgstr "da {begin} a {end}" -msgid "touristic-event" -msgstr "touristic-event" - msgid "Display order" msgstr "" diff --git a/geotrek/tourism/locale/nl/LC_MESSAGES/django.po b/geotrek/tourism/locale/nl/LC_MESSAGES/django.po index bfceadaa45..27ee523f6d 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -249,9 +249,6 @@ msgstr "" msgid "Touristic contents" msgstr "" -msgid "touristic-content" -msgstr "" - msgid "Published touristic contents" msgstr "" @@ -348,7 +345,7 @@ msgstr "" msgid "Price" msgstr "" -msgid "0 mean free" +msgid "0 means free" msgstr "" msgid "Select a place in the list or locate the event directly on the map" @@ -371,9 +368,6 @@ msgstr "" msgid "from {begin} to {end}" msgstr "" -msgid "touristic-event" -msgstr "" - msgid "Display order" msgstr "" diff --git a/geotrek/tourism/migrations/0053_alter_touristicevent_price.py b/geotrek/tourism/migrations/0053_alter_touristicevent_price.py new file mode 100644 index 0000000000..73ee6adebb --- /dev/null +++ b/geotrek/tourism/migrations/0053_alter_touristicevent_price.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.23 on 2024-03-18 12:00 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tourism', '0052_merge_20240229_1527'), + ] + + operations = [ + migrations.AlterField( + model_name='touristicevent', + name='price', + field=models.DecimalField(blank=True, decimal_places=2, help_text='0 means free', max_digits=8, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Price'), + ), + ] diff --git a/geotrek/tourism/models.py b/geotrek/tourism/models.py index ec7f5ad523..abd08021fa 100644 --- a/geotrek/tourism/models.py +++ b/geotrek/tourism/models.py @@ -30,12 +30,6 @@ TouristicContentType2Manager, TouristicContentManager, TouristicEventManager from geotrek.zoning.mixins import ZoningPropertiesMixin -from mapentity.serializers import plain_text - -if 'modeltranslation' in settings.INSTALLED_APPS: - pass -else: - pass logger = logging.getLogger(__name__) @@ -180,10 +174,6 @@ class Meta: def __str__(self): return self.label - @property - def prefixed_id(self): - return '{prefix}{id}'.format(prefix=self.id_prefix, id=self.id) - class TouristicContentType(OptionalPictogramMixin): objects = TouristicContentTypeFilteringManager() @@ -305,10 +295,6 @@ def type1_label(self): def type2_label(self): return self.category.type2_label - @property - def prefixed_category_id(self): - return self.category.prefixed_id - def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN @@ -321,15 +307,6 @@ def type(self): def extent(self): return self.geom.buffer(10).transform(settings.API_SRID, clone=True).extent - @property - def rando_url(self): - category_slug = _('touristic-content') - return '{}/{}/'.format(category_slug, self.slug) - - @property - def meta_description(self): - return plain_text(self.description_teaser or self.description)[:500] - @classmethod def topology_touristic_contents(cls, topology, queryset=None): return intersecting(qs=queryset_or_model(queryset, cls), obj=topology).order_by(*settings.TOURISTIC_CONTENTS_API_ORDER) @@ -470,7 +447,7 @@ class TouristicEvent(ZoningPropertiesMixin, AddPropertyMixin, PublishableMixin, max_digits=8, decimal_places=2, verbose_name=_("Price"), - help_text=_("0 mean free"), + help_text=_("0 means free"), validators=[MinValueValidator(0)] ) objects = TouristicEventManager() @@ -501,10 +478,6 @@ class Meta: def __str__(self): return self.name - @property - def type1(self): - return [self.type] if self.type else [] - @property def districts_display(self): return ', '.join([str(d) for d in self.districts]) @@ -521,22 +494,9 @@ def dates_display(self): begin=date_format(self.begin_date, 'SHORT_DATE_FORMAT'), end=date_format(self.end_date, 'SHORT_DATE_FORMAT')) - @property - def prefixed_category_id(self): - return self.id_prefix - def distance(self, to_cls): return settings.TOURISM_INTERSECTION_MARGIN - @property - def rando_url(self): - category_slug = _('touristic-event') - return '{}/{}/'.format(category_slug, self.slug) - - @property - def meta_description(self): - return plain_text(self.description_teaser or self.description)[:500] - @classmethod def topology_touristic_events(cls, topology, queryset=None): return intersecting(qs=queryset_or_model(queryset, cls), obj=topology) diff --git a/geotrek/tourism/serializers.py b/geotrek/tourism/serializers.py index 6ff6b59977..8f38ce305f 100644 --- a/geotrek/tourism/serializers.py +++ b/geotrek/tourism/serializers.py @@ -1,18 +1,10 @@ -from django.conf import settings -from django.utils.translation import gettext as _ from drf_dynamic_fields import DynamicFieldsMixin from mapentity.serializers import MapentityGeojsonModelSerializer from rest_framework import serializers as rest_serializers from rest_framework_gis import fields as rest_gis_fields from rest_framework_gis.serializers import GeoFeatureModelSerializer -from geotrek.authent.serializers import StructureSerializer -from geotrek.common.serializers import (ThemeSerializer, PublishableSerializerMixin, - PictogramSerializerMixin, RecordSourceSerializer, - PicturesSerializerMixin, TranslatedModelSerializer, - TargetPortalSerializer) -from geotrek.trekking import serializers as trekking_serializers -from geotrek.zoning.serializers import ZoningAPISerializerMixin +from geotrek.common.serializers import PictogramSerializerMixin, TranslatedModelSerializer from . import models as tourism_models @@ -28,61 +20,20 @@ class Meta: fields = ('id', 'pictogram', 'label') -class InformationDeskSerializer(TranslatedModelSerializer): +class TrekInformationDeskGeojsonSerializer(TranslatedModelSerializer, GeoFeatureModelSerializer): type = InformationDeskTypeSerializer() label_accessibility = LabelAccessibilitySerializer() - - class Meta: - model = tourism_models.InformationDesk - geo_field = 'geom' - fields = ('name', 'description', 'accessibility', 'label_accessibility', 'phone', 'email', 'website', - 'photo_url', 'street', 'postal_code', 'municipality', - 'latitude', 'longitude', 'type') - - -class InformationDeskGeojsonSerializer(GeoFeatureModelSerializer, InformationDeskSerializer): # Annotated geom field with API_SRID api_geom = rest_gis_fields.GeometryField(read_only=True, precision=7) - class Meta(InformationDeskSerializer.Meta): - geo_field = 'api_geom' - fields = InformationDeskSerializer.Meta.fields + ('api_geom', ) - - -class CloseTouristicContentSerializer(TranslatedModelSerializer): - category_id = rest_serializers.ReadOnlyField(source='prefixed_category_id') - - class Meta: - model = tourism_models.TouristicContent - fields = ('id', 'category_id') - - -class CloseTouristicEventSerializer(TranslatedModelSerializer): - category_id = rest_serializers.ReadOnlyField(source='prefixed_category_id') - class Meta: - model = tourism_models.TouristicEvent - fields = ('id', 'category_id') - - -class TouristicContentTypeSerializer(PictogramSerializerMixin, TranslatedModelSerializer): - name = rest_serializers.ReadOnlyField(source='label') - - class Meta: - model = tourism_models.TouristicContentType - fields = ('id', 'name', 'pictogram', 'in_list') - - -class TouristicContentCategorySerializer(PictogramSerializerMixin, TranslatedModelSerializer): - id = rest_serializers.ReadOnlyField(source='prefixed_id') - slug = rest_serializers.SerializerMethodField() - - class Meta: - model = tourism_models.TouristicContentCategory - fields = ('id', 'label', 'type1_label', 'type2_label', 'pictogram', 'order', 'slug') - - def get_slug(self, obj): - return _('touristic-content') + model = tourism_models.InformationDesk + geo_field = 'api_geom' + fields = ( + 'name', 'description', 'accessibility', 'label_accessibility', 'phone', 'email', 'website', + 'photo_url', 'street', 'postal_code', 'municipality', + 'latitude', 'longitude', 'type', 'api_geom', + ) class TouristicContentSerializer(DynamicFieldsMixin, rest_serializers.ModelSerializer): @@ -104,61 +55,6 @@ class Meta(MapentityGeojsonModelSerializer.Meta): fields = ('id', 'name') -class TouristicContentAPISerializer(PicturesSerializerMixin, PublishableSerializerMixin, ZoningAPISerializerMixin, - TranslatedModelSerializer): - themes = ThemeSerializer(many=True) - category = TouristicContentCategorySerializer() - label_accessibility = LabelAccessibilitySerializer() - type1 = TouristicContentTypeSerializer(many=True) - type2 = TouristicContentTypeSerializer(many=True) - source = RecordSourceSerializer(many=True) - portal = TargetPortalSerializer(many=True) - reservation_system = rest_serializers.ReadOnlyField(source='reservation_system.name', default="") - structure = StructureSerializer() - - # Nearby - touristic_contents = CloseTouristicContentSerializer(many=True, source='published_touristic_contents') - touristic_events = CloseTouristicEventSerializer(many=True, source='published_touristic_events') - treks = trekking_serializers.CloseTrekSerializer(many=True, source='published_treks') - pois = trekking_serializers.ClosePOISerializer(many=True, source='published_pois') - - def __init__(self, instance=None, *args, **kwargs): - super().__init__(instance, *args, **kwargs) - if 'geotrek.diving' in settings.INSTALLED_APPS: - - from geotrek.diving.serializers import CloseDiveSerializer - - self.fields['dives'] = CloseDiveSerializer(many=True, source='published_dives') - - class Meta: - model = tourism_models.TouristicContent - fields = ( - 'id', 'description', 'description_teaser', 'category', - 'themes', 'contact', 'email', 'website', 'practical_info', 'accessibility', 'label_accessibility', - 'type1', 'type2', 'touristic_contents', 'touristic_events', - 'treks', 'pois', 'source', 'portal', 'approved', - 'reservation_id', 'reservation_system', 'structure' - ) + ZoningAPISerializerMixin.Meta.fields + PublishableSerializerMixin.Meta.fields + \ - PicturesSerializerMixin.Meta.fields - - -class TouristicContentAPIGeojsonSerializer(GeoFeatureModelSerializer, TouristicContentAPISerializer): - # Annotated geom field with API_SRID - api_geom = rest_gis_fields.GeometryField(read_only=True, precision=7) - - class Meta(TouristicContentAPISerializer.Meta): - geo_field = 'api_geom' - fields = TouristicContentAPISerializer.Meta.fields + ('api_geom', ) - - -class TouristicEventTypeSerializer(PictogramSerializerMixin, TranslatedModelSerializer): - name = rest_serializers.ReadOnlyField(source='type') - - class Meta: - model = tourism_models.TouristicEventType - fields = ('id', 'name', 'pictogram') - - class TouristicEventSerializer(DynamicFieldsMixin, rest_serializers.ModelSerializer): name = rest_serializers.CharField(source='name_display') type = rest_serializers.SlugRelatedField('type', read_only=True) @@ -173,66 +69,3 @@ class TouristicEventGeojsonSerializer(MapentityGeojsonModelSerializer): class Meta(MapentityGeojsonModelSerializer.Meta): model = tourism_models.TouristicEvent fields = ('id', 'name') - - -class TouristicEventAPISerializer(PicturesSerializerMixin, PublishableSerializerMixin, - ZoningAPISerializerMixin, TranslatedModelSerializer): - themes = ThemeSerializer(many=True) - type = TouristicEventTypeSerializer() - source = RecordSourceSerializer(many=True) - portal = TargetPortalSerializer(many=True) - structure = StructureSerializer() - organizers = rest_serializers.SerializerMethodField(source="organizers") - - # Nearby - touristic_contents = CloseTouristicContentSerializer(many=True, source='published_touristic_contents') - touristic_events = CloseTouristicEventSerializer(many=True, source='published_touristic_events') - treks = trekking_serializers.CloseTrekSerializer(many=True, source='published_treks') - pois = trekking_serializers.ClosePOISerializer(many=True, source='published_pois') - - # For consistency with touristic contents - type1 = TouristicEventTypeSerializer(many=True) - category = rest_serializers.SerializerMethodField() - - def get_organizers(self, obj): - return ", ".join( - map(lambda org: org.label, obj.organizers.all()) - ) - - def __init__(self, instance=None, *args, **kwargs): - super().__init__(instance, *args, **kwargs) - if 'geotrek.diving' in settings.INSTALLED_APPS: - from geotrek.diving.serializers import CloseDiveSerializer - - self.fields['dives'] = CloseDiveSerializer(many=True, source='published_dives') - - class Meta: - model = tourism_models.TouristicEvent - fields = ( - 'id', 'accessibility', 'approved', 'begin_date', 'booking', - 'capacity', 'category', 'contact', 'description', 'description_teaser', - 'duration', 'email', 'end_date', 'end_time', 'meeting_point', 'organizers', - 'pois', 'portal', 'practical_info', 'source', 'speaker', 'start_time', - 'structure', 'target_audience', 'themes', 'touristic_contents', 'touristic_events', - 'treks', 'type', 'type1', 'website' - ) + ZoningAPISerializerMixin.Meta.fields + PublishableSerializerMixin.Meta.fields + \ - PicturesSerializerMixin.Meta.fields - - def get_category(self, obj): - return { - 'id': obj.prefixed_category_id, - 'order': settings.TOURISTIC_EVENT_CATEGORY_ORDER, - 'label': obj._meta.verbose_name_plural, - 'type1_label': obj._meta.get_field('type').verbose_name, - 'pictogram': '/static/tourism/touristicevent.svg', - 'slug': _('touristic-event'), - } - - -class TouristicEventAPIGeojsonSerializer(GeoFeatureModelSerializer, TouristicEventAPISerializer): - # Annotated geom field with API_SRID - api_geom = rest_gis_fields.GeometryField(read_only=True, precision=7) - - class Meta(TouristicEventAPISerializer.Meta): - geo_field = 'api_geom' - fields = TouristicEventAPISerializer.Meta.fields + ('api_geom', ) diff --git a/geotrek/tourism/templates/tourism/touristiccontent_meta.html b/geotrek/tourism/templates/tourism/touristiccontent_meta.html deleted file mode 100644 index 554be0e922..0000000000 --- a/geotrek/tourism/templates/tourism/touristiccontent_meta.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %}{% get_current_language as LANGUAGE_CODE %} - - - - - {{ object.name }} - - - - - - {% if object.resized_pictures %} - - - - {% else %} - - - - {% endif %} - - - -

{{ object.name }}

-{{ META_TITLE }} - - diff --git a/geotrek/tourism/templates/tourism/touristicevent_meta.html b/geotrek/tourism/templates/tourism/touristicevent_meta.html deleted file mode 100644 index 554be0e922..0000000000 --- a/geotrek/tourism/templates/tourism/touristicevent_meta.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %}{% get_current_language as LANGUAGE_CODE %} - - - - - {{ object.name }} - - - - - - {% if object.resized_pictures %} - - - - {% else %} - - - - {% endif %} - - - -

{{ object.name }}

-{{ META_TITLE }} - - diff --git a/geotrek/tourism/tests/test_filters.py b/geotrek/tourism/tests/test_filters.py index ab9c6520fa..3eaf8cb899 100644 --- a/geotrek/tourism/tests/test_filters.py +++ b/geotrek/tourism/tests/test_filters.py @@ -1,8 +1,8 @@ from django.test import TestCase -from geotrek.tourism.tests.factories import TouristicEventFactory +from geotrek.tourism.tests.factories import TouristicContentFactory, TouristicEventFactory from geotrek.tourism.models import TouristicEvent -from geotrek.tourism.filters import CompletedFilter, TouristicEventFilterSet +from geotrek.tourism.filters import CompletedFilter, TouristicEventFilterSet, TouristicContentFilterSet class TouristicEventFilterSetTestCase(TestCase): @@ -37,3 +37,53 @@ def test_after_filter(self): self.assertEqual(filter.qs.count(), 1) filter = self.filter_class(data={'after': '2300-01-01'}) self.assertEqual(filter.qs.count(), 0) + + +class TouristicContentFilterTest(TestCase): + factory = TouristicContentFactory + filterset = TouristicContentFilterSet + + def test_provider_filter_without_provider(self): + filter_set = TouristicContentFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + touristic_content1 = TouristicContentFactory.create(provider='my_provider1') + touristic_content2 = TouristicContentFactory.create(provider='my_provider2') + + filter_set = TouristicContentFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(touristic_content1, filter_set.qs) + self.assertIn(touristic_content2, filter_set.qs) + + +class TouristicEventFilterTest(TestCase): + factory = TouristicEventFactory + filterset = TouristicEventFilterSet + + def test_provider_filter_without_provider(self): + filter_set = TouristicEventFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + touristic_event1 = TouristicEventFactory.create(provider='my_provider1') + touristic_event2 = TouristicEventFactory.create(provider='my_provider2') + + filter_set = TouristicEventFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(touristic_event1, filter_set.qs) + self.assertIn(touristic_event2, filter_set.qs) diff --git a/geotrek/tourism/tests/test_functional.py b/geotrek/tourism/tests/test_functional.py index a915e29a35..92aa49653b 100644 --- a/geotrek/tourism/tests/test_functional.py +++ b/geotrek/tourism/tests/test_functional.py @@ -1,32 +1,33 @@ -from django.contrib.gis.geos import Polygon, MultiPolygon +import csv +import filecmp +import os +from io import StringIO +from operator import attrgetter +from unittest.mock import patch + from django.conf import settings +from django.contrib.gis.geos import MultiPolygon, Polygon from django.test.utils import override_settings -from django.utils.translation import gettext_lazy as _ from django.utils.module_loading import import_string +from django.utils.translation import gettext_lazy as _ -import filecmp -from geotrek.authent.tests.factories import StructureFactory -from geotrek.authent.tests.factories import TrekkingManagerFactory +from geotrek.authent.tests.factories import StructureFactory, TrekkingManagerFactory +from geotrek.common.tests import CommonTest from geotrek.common.tests.factories import AttachmentFactory -from geotrek.common.tests import CommonTest, GeotrekAPITestCase from geotrek.common.utils.testdata import get_dummy_uploaded_image -from geotrek.tourism.models import (TouristicContent, - TouristicEvent) -from geotrek.tourism.tests.factories import (TouristicContentFactory, - TouristicContentCategoryFactory, - TouristicEventFactory, - TouristicEventParticipantCountFactory, - TouristicEventParticipantCategoryFactory) +from geotrek.tourism.models import TouristicContent, TouristicEvent from geotrek.zoning.tests.factories import CityFactory -from unittest.mock import patch -import os -import csv -from io import StringIO -from operator import attrgetter +from .factories import ( + TouristicContentCategoryFactory, + TouristicContentFactory, + TouristicEventFactory, + TouristicEventParticipantCategoryFactory, + TouristicEventParticipantCountFactory, +) -class TouristicContentViewsTests(GeotrekAPITestCase, CommonTest): +class TouristicContentViewsTests(CommonTest): model = TouristicContent modelfactory = TouristicContentFactory userfactory = TrekkingManagerFactory @@ -42,82 +43,6 @@ def get_expected_geojson_attrs(self): 'name': self.obj.name } - def get_expected_json_attrs(self): - return { - 'accessibility': 'Accessible', - 'approved': False, - 'areas': [], - 'category': { - 'id': 'C{}'.format(self.obj.category.pk), - 'label': 'Category', - 'order': None, - 'pictogram': '/media/upload/touristiccontent-category.png', - 'slug': 'touristic-content', - 'type1_label': 'Type1 label', - 'type2_label': '', - }, - 'cities': [], - 'contact': '', - 'description': '

Blah CT

', - 'description_teaser': '', - 'districts': [], - 'dives': [], - 'email': None, - 'filelist_url': '/paperclip/get/tourism/touristiccontent/{}/'.format(self.obj.pk), - 'files': [], - 'label_accessibility': { - 'id': self.obj.label_accessibility.pk, - 'label': self.obj.label_accessibility.label, - 'pictogram': '/media/upload/dummy_img.png' - }, - 'map_image_url': '/image/touristiccontent-{}.png'.format(self.obj.pk), - 'name': 'Touristic content', - 'pictures': [], - 'pois': [], - 'portal': [{ - 'name': self.obj.portal.get().name, - 'website': self.obj.portal.get().website - }], - 'practical_info': '', - 'printable': '/api/en/touristiccontents/{}/touristic-content.pdf'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False}, - ], - 'reservation_id': 'XXXXXXXXX', - 'reservation_system': self.obj.reservation_system.name, - 'slug': 'touristic-content', - 'source': [], - 'structure': {'id': self.obj.structure.pk, 'name': 'My structure'}, - 'themes': [{ - 'id': self.obj.themes.get().pk, - 'label': self.obj.themes.get().label, - 'pictogram': self.obj.themes.get().pictogram.url, - }], - 'thumbnail': None, - 'touristic_contents': [], - 'touristic_events': [], - 'treks': [], - 'type1': [{ - 'id': self.obj.type1.get().pk, - 'in_list': 1, - 'name': 'Type1', - 'pictogram': '/media/upload/touristiccontent-type1.png' - }], - 'type2': [{ - 'id': self.obj.type2.get().pk, - 'in_list': 2, - 'name': 'Type2', - 'pictogram': '/media/upload/touristiccontent-type2.png' - }], - 'videos': [], - 'website': None, - } - def get_expected_datatables_attrs(self): return { 'category': self.obj.category.label, @@ -172,7 +97,7 @@ def test_custom_columns_mixin_on_export(self): ['id', 'type1', 'type2', 'eid']) -class TouristicEventViewsTests(GeotrekAPITestCase, CommonTest): +class TouristicEventViewsTests(CommonTest): model = TouristicEvent modelfactory = TouristicEventFactory userfactory = TrekkingManagerFactory @@ -188,80 +113,6 @@ def get_expected_geojson_attrs(self): 'name': self.obj.name } - def get_expected_json_attrs(self): - return { - 'accessibility': '', - 'approved': False, - 'areas': [], - 'begin_date': '2002-02-20', - 'booking': '', - 'category': { - 'id': 'E', - 'label': 'Touristic events', - 'order': 99, - 'pictogram': '/static/tourism/touristicevent.svg', - 'slug': 'touristic-event', - 'type1_label': 'Type', - }, - 'cities': [], - 'contact': '', - 'description': '', - 'description_teaser': '', - 'districts': [], - 'dives': [], - 'duration': '', - 'email': None, - 'end_date': '2202-02-22', - 'filelist_url': '/paperclip/get/tourism/touristicevent/{}/'.format(self.obj.pk), - 'files': [], - 'map_image_url': '/image/touristicevent-{}.png'.format(self.obj.pk), - 'meeting_point': '', - 'start_time': None, - 'end_time': None, - 'name': 'Touristic event', - 'organizers': '', - 'capacity': None, - 'pictures': [], - 'pois': [], - 'portal': [], - 'practical_info': '', - 'printable': '/api/en/touristicevents/{}/touristic-event.pdf'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False}, - ], - 'slug': 'touristic-event', - 'source': [], - 'speaker': '', - 'structure': {'id': self.obj.structure.pk, 'name': 'My structure'}, - 'target_audience': None, - 'themes': [{ - 'id': self.obj.themes.get().pk, - 'label': self.obj.themes.get().label, - 'pictogram': self.obj.themes.get().pictogram.url, - }], - 'thumbnail': None, - 'touristic_contents': [], - 'touristic_events': [], - 'treks': [], - 'type': { - 'id': self.obj.type1[0].pk, - 'name': 'Type', - 'pictogram': '/media/upload/touristicevent-type.png' - }, - 'type1': [{ - 'id': self.obj.type1[0].pk, - 'name': 'Type', - 'pictogram': '/media/upload/touristicevent-type.png' - }], - 'videos': [], - 'website': None, - } - def get_expected_datatables_attrs(self): return { 'begin_date': '20/02/2002', diff --git a/geotrek/tourism/tests/test_helpers_sync.py b/geotrek/tourism/tests/test_helpers_sync.py deleted file mode 100644 index 4163f9f3b9..0000000000 --- a/geotrek/tourism/tests/test_helpers_sync.py +++ /dev/null @@ -1,115 +0,0 @@ -import os -from unittest.mock import patch -from io import StringIO - -from django.test import TestCase -from django.conf import settings - -from geotrek.common.tests.factories import FakeSyncCommand, RecordSourceFactory, TargetPortalFactory, AttachmentFactory -from geotrek.common.utils.testdata import get_dummy_uploaded_image -from geotrek.tourism.tests.factories import InformationDeskFactory, TouristicContentFactory, TouristicEventFactory - -from geotrek.tourism.helpers_sync import SyncRando - - -@patch('geotrek.tourism.models.TouristicContent.prepare_map_image') -@patch('geotrek.tourism.models.TouristicEvent.prepare_map_image') -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.source = RecordSourceFactory() - cls.portal = TargetPortalFactory() - - cls.information_desks = InformationDeskFactory.create() - - cls.touristic_content = TouristicContentFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.touristic_event = TouristicEventFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.attachment_touristic_content = AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - cls.attachment_touristic_event = AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - - AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync(self, stdout, mock_prepare_event, mock_prepare_content): - def side_effect_sync_event(lang, event): - self.assertEqual(event, self.touristic_event) - - def side_effect_sync_content(lang, content): - self.assertEqual(content, self.touristic_content) - - command = FakeSyncCommand() - synchro = SyncRando(command) - with patch('geotrek.tourism.helpers_sync.SyncRando.sync_event', side_effect=side_effect_sync_event): - with patch('geotrek.tourism.helpers_sync.SyncRando.sync_content', side_effect=side_effect_sync_content): - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'static', 'tourism', - 'touristicevent.svg'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'information_desks.geojson'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_portal_source(self, stdout, mock_prepare_event, mock_prepare_content): - def side_effect_sync_event(lang, event): - self.assertEqual(event, self.touristic_event_p_s) - - def side_effect_sync_content(lang, content): - self.assertEqual(content, self.touristic_content_p_s) - - self.touristic_content_p_s = TouristicContentFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, sources=(self.source,), - portals=(self.portal,), published=True) - self.touristic_event_p_s = TouristicEventFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, sources=(self.source,), - portals=(self.portal,), published=True) - - command = FakeSyncCommand(portal=self.portal.name, source=[self.source.name]) - synchro = SyncRando(command) - with patch('geotrek.tourism.helpers_sync.SyncRando.sync_event', side_effect=side_effect_sync_event): - with patch('geotrek.tourism.helpers_sync.SyncRando.sync_content', side_effect=side_effect_sync_content): - synchro.sync('en') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'static', 'tourism', - 'touristicevent.svg'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'information_desks.geojson'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_event(self, stdout, mock_prepare_event, mock_prepare_content): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync_event('fr', self.touristic_event) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'touristicevents', - str(self.touristic_event.pk), - '%s.pdf' % self.touristic_event.slug))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_content(self, stdout, mock_prepare_event, mock_prepare_content): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync_content('fr', self.touristic_content) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'touristiccontents', - str(self.touristic_content.pk), - '%s.pdf' % self.touristic_content.slug))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_event_portal_source(self, stdout, mock_prepare_event, mock_prepare_content): - command = FakeSyncCommand(portal=self.portal.name, source=[self.source.name]) - synchro = SyncRando(command) - synchro.sync_event('fr', self.touristic_event) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'touristicevents', - str(self.touristic_event.pk), - '%s.pdf' % self.touristic_event.slug))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_content_portal_source(self, stdout, mock_prepare_event, mock_prepare_content): - command = FakeSyncCommand(portal=self.portal.name, source=[self.source.name]) - synchro = SyncRando(command) - synchro.sync_content('fr', self.touristic_content) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'touristiccontents', - str(self.touristic_content.pk), - '%s.pdf' % self.touristic_content.slug))) diff --git a/geotrek/tourism/tests/test_views.py b/geotrek/tourism/tests/test_views.py index dc79694507..8fcc4cc74e 100644 --- a/geotrek/tourism/tests/test_views.py +++ b/geotrek/tourism/tests/test_views.py @@ -1,7 +1,3 @@ -import hashlib -import json -import os -from datetime import datetime from shutil import rmtree from tempfile import mkdtemp from unittest import mock @@ -12,29 +8,18 @@ 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 embed_video.backends import detect_backend from paperclip.models import random_suffix_regexp from geotrek.authent.tests.base import AuthentFixturesTest from geotrek.authent.tests.factories import (StructureFactory, UserFactory, UserProfileFactory) from geotrek.common.models import Attachment, FileType -from geotrek.common.tests import TranslationResetMixin -from geotrek.common.tests import factories as common_factories -from geotrek.common.utils.testdata import (get_dummy_uploaded_document, - get_dummy_uploaded_image) -from geotrek.core.tests import factories as core_factories from geotrek.tourism.tests.factories import (InformationDeskFactory, TouristicContentCategoryFactory, TouristicContentFactory, - TouristicContentType1Factory, - TouristicContentType2Factory, TouristicEventFactory) -from geotrek.tourism.filters import TouristicContentFilterSet, TouristicEventFilterSet from geotrek.trekking.tests import factories as trekking_factories from geotrek.trekking.tests.base import TrekkingManagerTest -from geotrek.zoning.tests import factories as zoning_factories PNG_BLACK_PIXEL = bytes.fromhex( '89504e470d0a1a0a0000000d4948445200000001000000010804000000b51c0c0200' @@ -117,8 +102,10 @@ def test_shown_in_details_when_enabled(self): self.assertContains(response, 'Tourism') @override_settings(TOURISM_ENABLED=False) - def test_not_shown_in_details_when_disabled(self): - url = "/touristiccontent/%s/" % self.content.pk + def test_not_tourism_detail_fragment_displayed(self): + """Test in other module, if tourism_detail_fragment.html is not displayed.""" + trek = trekking_factories.TrekFactory.create() + url = "/trek/%s/" % trek.pk response = self.client.get(url) self.assertNotContains(response, 'Tourism') @@ -148,259 +135,6 @@ def test_default_category_is_taken_from_url_params(self): self.assertContains(response, 'value="%s" selected' % self.category.pk) -class BasicJSONAPITest(TranslationResetMixin): - factory = None - - @classmethod - def setUpTestData(cls): - polygon = 'SRID=%s;MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))' % settings.SRID - cls.city = zoning_factories.CityFactory(geom=polygon) - cls.district = zoning_factories.DistrictFactory(geom=polygon) - cls.portal = common_factories.TargetPortalFactory() - cls.theme = common_factories.ThemeFactory() - - cls.content = cls.factory(geom='SRID=%s;POINT(1 1)' % settings.SRID, - portals=[cls.portal], themes=[cls.theme]) - - cls.picture = common_factories.AttachmentFactory(content_object=cls.content, - attachment_file=get_dummy_uploaded_image()) - cls.document = common_factories.AttachmentFactory(content_object=cls.content, - attachment_file=get_dummy_uploaded_document()) - - cls.content.themes.add(cls.theme) - cls.source = common_factories.RecordSourceFactory() - cls.content.source.add(cls.source) - - cls.content.portal.add(cls.portal) - if settings.TREKKING_TOPOLOGY_ENABLED: - path = core_factories.PathFactory(geom='SRID=%s;LINESTRING(0 10, 10 10)' % settings.SRID) - cls.trek = trekking_factories.TrekFactory(paths=[path]) - cls.poi = trekking_factories.POIFactory(paths=[(path, 0.5, 0.5)]) - else: - cls.trek = trekking_factories.TrekFactory(geom='SRID=%s;LINESTRING(0 10, 10 10)' % settings.SRID) - cls.poi = trekking_factories.POIFactory(geom='SRID=%s;POINT(0 5)' % settings.SRID) - - @override_settings(THUMBNAIL_COPYRIGHT_FORMAT="{title} {author}") - def setUp(self): - super().setUp() - self.pk = self.content.pk - url = '/api/en/{model}s/{pk}.json'.format(model=self.content._meta.model_name, pk=self.pk) - self.response = self.client.get(url) - self.result = self.response.json() - - def test_thumbnail(self): - self.assertEqual(self.result['thumbnail'], - self.picture.attachment_file.url + '.120x120_q85_crop.png') - - def test_published_status(self): - self.assertDictEqual(self.result['published_status'][0], - {'lang': 'en', 'status': True, 'language': 'English'}) - - @override_settings(THUMBNAIL_COPYRIGHT_FORMAT="{title} {author}") - def test_pictures(self): - url = '{url}.800x800_q85_watermark-{id}.png'.format( - url=self.picture.attachment_file.url, - id=hashlib.md5( - settings.THUMBNAIL_COPYRIGHT_FORMAT.format( - author=self.picture.author, - title=self.picture.title, - legend=self.picture.legend).encode()).hexdigest()) - self.assertDictEqual(self.result['pictures'][0], - {'url': url, - 'title': self.picture.title, - 'legend': self.picture.legend, - 'author': self.picture.author}) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_files(self): - self.assertDictEqual(self.result['files'][0], - {'url': os.path.join(settings.MEDIA_URL, self.document.attachment_file.name), - 'title': self.document.title, - 'legend': self.document.legend, - 'author': self.document.author}) - - def test_video_youtube(self): - video_youtube = common_factories.AttachmentFactory(content_object=self.content, attachment_file='', - attachment_video='https://www.youtube.com/embed/Jm3anSjly0Y?wmode=opaque') - video_detected_youtube = detect_backend(video_youtube.attachment_video) - pk = self.content.pk - url = '/api/en/{model}s/{pk}.json'.format(model=self.content._meta.model_name, pk=pk) - response = self.client.get(url) - result = response.json() - self.assertDictEqual(result['videos'][0], - {'backend': 'Youtube', - 'url': 'https://www.youtube.com/embed/Jm3anSjly0Y?wmode=opaque', - 'title': video_youtube.title, - 'legend': video_youtube.legend, - 'author': video_youtube.author, - 'code': video_detected_youtube.code}) - - def test_video_dailymotion(self): - video_dailymotion = common_factories.AttachmentFactory( - content_object=self.content, attachment_file='', - attachment_video='https://www.dailymotion.com/video/x6e0q24') - video_detected_dailymotion = detect_backend(video_dailymotion.attachment_video) - pk = self.content.pk - url = '/api/en/{model}s/{pk}.json'.format(model=self.content._meta.model_name, pk=pk) - response = self.client.get(url) - result = response.json() - - self.assertDictEqual(result['videos'][0], - {'backend': 'Dailymotion', - 'url': 'https://www.dailymotion.com/embed/video/x6e0q24', - 'title': video_dailymotion.title, - 'legend': video_dailymotion.legend, - 'author': video_dailymotion.author, - 'code': video_detected_dailymotion.code}) - - def test_video_dailymotion_wrong_id(self): - common_factories.AttachmentFactory( - content_object=self.content, attachment_file='', - attachment_video='https://www.dailymotion.com/video/noid') - - pk = self.content.pk - url = '/api/en/{model}s/{pk}.json'.format(model=self.content._meta.model_name, pk=pk) - response = self.client.get(url) - result = json.loads(response.content.decode()) - self.assertFalse(result['videos']) - - def test_cities(self): - self.assertDictEqual(self.result['cities'][0], - {"code": self.city.code, - "name": self.city.name}) - - def test_districts(self): - self.assertDictEqual(self.result['districts'][0], - {"id": self.district.id, - "name": self.district.name}) - - def test_themes(self): - self.assertDictEqual(self.result['themes'][0], - {"id": self.theme.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.theme.pictogram.name), - "label": self.theme.label}) - - def test_treks(self): - self.assertDictEqual(self.result['treks'][0], { - 'id': self.trek.id, - 'category_id': 'T'}) - - def test_pois(self): - self.assertDictEqual(self.result['pois'][0], { - 'id': self.poi.id, - 'slug': self.poi.slug, - 'name': self.poi.name, - 'type': { - 'id': self.poi.type.id, - 'label': self.poi.type.label, - 'pictogram': os.path.join(settings.MEDIA_URL, self.poi.type.pictogram.name)}}) - - def test_sources(self): - self.assertDictEqual(self.result['source'][0], { - 'name': self.source.name, - 'website': self.source.website, - "pictogram": os.path.join(settings.MEDIA_URL, self.source.pictogram.name)}) - - def test_portals(self): - ''' - Test if portal correctly serialized - ''' - self.assertDictEqual( - self.result['portal'][0], - {'name': self.portal.name, - 'website': self.portal.website, }) - - def test_approved(self): - self.assertFalse(self.result['approved']) - - -class TouristicContentAPITest(BasicJSONAPITest, TrekkingManagerTest): - factory = TouristicContentFactory - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.category = cls.content.category - cls.type1 = TouristicContentType1Factory(category=cls.category) - cls.type2 = TouristicContentType2Factory(category=cls.category, pictogram=None) - cls.content.type1.set([cls.type1]) - cls.content.type2.set([cls.type2]) - - def test_expected_properties(self): - self.assertEqual(sorted([ - 'accessibility', 'approved', 'areas', 'category', 'cities', 'contact', - 'description', 'description_teaser', 'districts', 'email', - 'filelist_url', 'files', 'id', 'label_accessibility', 'map_image_url', 'name', 'pictures', - 'pois', 'practical_info', 'printable', 'publication_date', - 'published', 'published_status', 'reservation_id', 'reservation_system', - 'slug', 'source', 'structure', 'portal', 'themes', 'thumbnail', 'touristic_contents', - 'touristic_events', 'treks', 'type1', 'type2', 'videos', 'website', 'dives']), - sorted(self.result.keys())) - - def test_type1(self): - self.assertDictEqual(self.result['type1'][0], - {"id": self.type1.id, - "name": self.type1.label, - 'pictogram': os.path.join(settings.MEDIA_URL, self.type1.pictogram.name), - "in_list": self.type1.in_list}) - - def test_type2(self): - self.assertDictEqual(self.result['type2'][0], - {"id": self.type2.id, - "name": self.type2.label, - 'pictogram': None, - "in_list": self.type2.in_list}) - - def test_category(self): - self.assertDictEqual(self.result['category'], { - "id": self.category.prefixed_id, - "order": None, - "label": self.category.label, - "slug": "touristic-content", - "type1_label": self.content.type1_label, - "type2_label": self.content.type2_label, - "pictogram": os.path.join(settings.MEDIA_URL, self.category.pictogram.name)}) - - -class TouristicEventAPITest(BasicJSONAPITest, TrekkingManagerTest): - factory = TouristicEventFactory - - def test_expected_properties(self): - self.assertEqual(sorted([ - 'accessibility', 'approved', 'areas', 'begin_date', 'booking', 'category', - 'cities', 'contact', 'description', 'description_teaser', - 'districts', 'duration', 'email', 'end_date', 'filelist_url', 'files', - 'id', 'map_image_url', 'meeting_point', 'start_time', 'end_time', 'name', - 'organizers', 'capacity', 'pictures', 'pois', 'portal', 'practical_info', - 'printable', 'publication_date', 'published', 'published_status', - 'slug', 'source', 'speaker', 'structure', 'target_audience', 'themes', - 'thumbnail', 'touristic_contents', 'touristic_events', 'treks', 'type', - 'type1', 'videos', 'website', 'dives']), - sorted(self.result.keys())) - - def test_type(self): - self.assertDictEqual(self.result['type'], - {"id": self.content.type.id, - 'pictogram': os.path.join(settings.MEDIA_URL, self.content.type.pictogram.name), - "name": self.content.type.type}) - - def test_type1(self): - self.assertDictEqual(self.result['type1'][0], - {"id": self.content.type.id, - 'pictogram': os.path.join(settings.MEDIA_URL, self.content.type.pictogram.name), - "name": self.content.type.type}) - - def test_category(self): - self.assertDictEqual(self.result['category'], - {"id": 'E', - "order": 99, - "label": "Touristic events", - "slug": "touristic-event", - "type1_label": "Type", - "pictogram": "/static/tourism/touristicevent.svg"}) - - class TouristicEventViewsSameStructureTests(AuthentFixturesTest): @classmethod def setUpTestData(cls): @@ -527,56 +261,6 @@ def test_not_published_document_pdf(self): self.assertEqual(response.status_code, 403) -class TouristicEventViewSetTest(TestCase): - def test_touristic_events_without_enddate_filter(self): - TouristicEventFactory.create_batch(10, published=True) - response = self.client.get('/api/en/touristicevents.geojson') - geojson = response.json() - self.assertEqual(len(geojson['features']), 10) - - def test_touristic_events_with_enddate_filter(self): - """ - Relative date: 2020-01-01 - 5 events with no end date - 5 events with end date after relative date - 7 events with end date before relative date - -> only events with no end or end in after relative date must be included - """ - TouristicEventFactory.create_batch(5, end_date=None, published=True) - TouristicEventFactory.create_batch(5, end_date=datetime.strptime('2020-05-10', '%Y-%m-%d'), published=True) - TouristicEventFactory.create_batch(7, end_date=datetime.strptime('2010-05-10', '%Y-%m-%d'), published=True) - response = self.client.get('/api/en/touristicevents.geojson', data={'ends_after': '2020-01-01'}) - geojson = response.json() - - self.assertEqual(len(geojson['features']), 10) - - -class TouristicCategoryViewTest(TestCase): - def test_get_categories(self): - """ - Test category json serialization via api - """ - nb_elements = 10 - TouristicContentCategoryFactory.create_batch(nb_elements) - response = self.client.get(reverse('tourism:touristic_categories_json', kwargs={'lang': 'en'})) - json_response = response.json() - self.assertEqual(len(json_response), nb_elements) - - -class InformationDeskAPITest(TestCase): - def test_geojson(self): - InformationDeskFactory.create() - desk2 = InformationDeskFactory.create() - response = self.client.get('/api/en/information_desks-{}.geojson'.format(desk2.type.id)) - self.assertEqual(response.status_code, 200) - result = response.json() - self.assertIn('features', result) - self.assertEqual(len(result['features']), 1) - self.assertEqual(result['features'][0]['type'], 'Feature') - self.assertEqual(result['features'][0]['geometry']['type'], 'Point') - self.assertEqual(result['features'][0]['properties']['name'], desk2.name) - - class TrekInformationDeskAPITest(TestCase): def test_geojson(self): trek = trekking_factories.TrekFactory() @@ -591,53 +275,3 @@ def test_geojson(self): self.assertEqual(result['features'][0]['type'], 'Feature') self.assertEqual(result['features'][0]['geometry']['type'], 'Point') self.assertEqual(result['features'][0]['properties']['name'], desk.name) - - -class TouristicContentFilterTest(TestCase): - factory = TouristicContentFactory - filterset = TouristicContentFilterSet - - def test_provider_filter_without_provider(self): - filter_set = TouristicContentFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - touristic_content1 = TouristicContentFactory.create(provider='my_provider1') - touristic_content2 = TouristicContentFactory.create(provider='my_provider2') - - filter_set = TouristicContentFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(touristic_content1, filter_set.qs) - self.assertIn(touristic_content2, filter_set.qs) - - -class TouristicEventFilterTest(TestCase): - factory = TouristicEventFactory - filterset = TouristicEventFilterSet - - def test_provider_filter_without_provider(self): - filter_set = TouristicEventFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - touristic_event1 = TouristicEventFactory.create(provider='my_provider1') - touristic_event2 = TouristicEventFactory.create(provider='my_provider2') - - filter_set = TouristicEventFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(touristic_event1, filter_set.qs) - self.assertIn(touristic_event2, filter_set.qs) diff --git a/geotrek/tourism/urls.py b/geotrek/tourism/urls.py index 08dd7508a5..aa77399cd7 100644 --- a/geotrek/tourism/urls.py +++ b/geotrek/tourism/urls.py @@ -2,26 +2,17 @@ from django.urls import path, re_path, register_converter from mapentity.registry import registry -from rest_framework.routers import DefaultRouter from geotrek.common.urls import PublishableEntityOptions, LangConverter from . import models from . import views as tourism_views -from .views import TouristicContentAPIViewSet, TouristicEventAPIViewSet register_converter(LangConverter, 'lang') app_name = 'tourism' urlpatterns = [ - re_path(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/information_desks.geojson$', tourism_views.InformationDeskViewSet.as_view({'get': 'list'}), name="information_desk_geojson"), - re_path(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/information_desks-(?P\d+)\.geojson$', tourism_views.InformationDeskViewSet.as_view({'get': 'list'})), re_path(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/treks/(?P\d+)/information_desks.geojson$', tourism_views.TrekInformationDeskViewSet.as_view({'get': 'list'}), name="trek_information_desk_geojson"), - re_path(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/treks/(?P\d+)/touristicevents\.geojson$', tourism_views.TrekTouristicEventViewSet.as_view({'get': 'list'}), name="trek_events_geojson"), - re_path(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/treks/(?P\d+)/touristiccontents\.geojson$', tourism_views.TrekTouristicContentViewSet.as_view({'get': 'list'}), name="trek_contents_geojson"), - path('api//touristiccategories.json', tourism_views.TouristicCategoryView.as_view(), name="touristic_categories_json"), - path('api//touristiccontents//meta.html', tourism_views.TouristicContentMeta.as_view(), name="touristiccontent_meta"), - path('api//touristicevents//meta.html', tourism_views.TouristicEventMeta.as_view(), name="touristicevent_meta"), path('popup/add/organizer/', tourism_views.TouristicEventOrganizerCreatePopup.as_view(), name='organizer_add'), ] @@ -32,24 +23,14 @@ class TouristicContentEntityOptions(PublishableEntityOptions): markup_public_view = tourism_views.TouristicContentMarkupPublic -if settings.TOURISM_ENABLED: - urlpatterns += registry.register(models.TouristicContent, TouristicContentEntityOptions, - menu=settings.TOURISTICCONTENT_MODEL_ENABLED) - - class TouristicEventEntityOptions(PublishableEntityOptions): document_public_view = tourism_views.TouristicEventDocumentPublic document_public_booklet_view = tourism_views.TouristicEventDocumentBookletPublic markup_public_view = tourism_views.TouristicEventMarkupPublic -router = DefaultRouter(trailing_slash=False) - -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/touristiccontents', TouristicContentAPIViewSet, basename='touristiccontent') -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/touristicevents', TouristicEventAPIViewSet, basename='touristicevent') - -urlpatterns += router.urls - if settings.TOURISM_ENABLED: urlpatterns += registry.register(models.TouristicEvent, TouristicEventEntityOptions, menu=settings.TOURISTICEVENT_MODEL_ENABLED) + urlpatterns += registry.register(models.TouristicContent, TouristicContentEntityOptions, + menu=settings.TOURISTICCONTENT_MODEL_ENABLED) diff --git a/geotrek/tourism/views.py b/geotrek/tourism/views.py index 60a8b583a4..693d06a4c4 100644 --- a/geotrek/tourism/views.py +++ b/geotrek/tourism/views.py @@ -1,44 +1,33 @@ import logging -import os from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.gis.db.models.functions import Transform from django.core.exceptions import PermissionDenied -from django.db.models import Q, Sum -from django.http import Http404, HttpResponse +from django.db.models import Sum +from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator -from django.utils.translation import gettext as _ from django.utils.html import escape -from django.views.generic import DetailView, CreateView -from django_filters.rest_framework import DjangoFilterBackend +from django.views.generic import CreateView from mapentity.views import (MapEntityCreate, MapEntityUpdate, MapEntityList, MapEntityDetail, MapEntityDelete, MapEntityFormat, MapEntityDocument) from rest_framework import permissions as rest_permissions, viewsets -from rest_framework.permissions import IsAuthenticatedOrReadOnly -from rest_framework.renderers import JSONRenderer -from rest_framework.response import Response -from rest_framework.views import APIView from geotrek.authent.decorators import same_structure_required -from geotrek.common.mixins.api import APIViewSet -from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin, MetaMixin +from geotrek.common.mixins.views import CompletenessMixin, CustomColumnsMixin from geotrek.common.models import RecordSource, TargetPortal from geotrek.common.views import DocumentPublic, DocumentBookletPublic, MarkupPublic from geotrek.common.viewsets import GeotrekMapentityViewSet from geotrek.trekking.models import Trek -from .filters import TouristicContentFilterSet, TouristicEventFilterSet, TouristicEventApiFilterSet +from .filters import TouristicContentFilterSet, TouristicEventFilterSet from .forms import TouristicContentForm, TouristicEventForm, TouristicEventOrganizerFormPopup -from .models import (TouristicContent, TouristicEvent, TouristicContentCategory, TouristicEventOrganizer, InformationDesk) +from .models import (TouristicContent, TouristicEvent, TouristicContentCategory, TouristicEventOrganizer, + InformationDesk) from .serializers import (TouristicContentSerializer, TouristicEventSerializer, - TouristicContentAPIGeojsonSerializer, TouristicEventAPIGeojsonSerializer, - InformationDeskGeojsonSerializer, TouristicContentAPISerializer, TouristicEventAPISerializer, + TrekInformationDeskGeojsonSerializer, TouristicContentGeojsonSerializer, TouristicEventGeojsonSerializer) -if 'geotrek.diving' in settings.INSTALLED_APPS: - from geotrek.diving.models import Dive - logger = logging.getLogger(__name__) @@ -152,11 +141,6 @@ class TouristicContentMarkupPublic(TouristicContentDocumentPublicMixin, MarkupPu pass -class TouristicContentMeta(MetaMixin, DetailView): - model = TouristicContent - template_name = 'tourism/touristiccontent_meta.html' - - class TouristicContentViewSet(GeotrekMapentityViewSet): model = TouristicContent serializer_class = TouristicContentSerializer @@ -172,25 +156,6 @@ def get_queryset(self): return qs -class TouristicContentAPIViewSet(APIViewSet): - model = TouristicContent - serializer_class = TouristicContentAPISerializer - geojson_serializer_class = TouristicContentAPIGeojsonSerializer - - def get_queryset(self): - qs = TouristicContent.objects.existing() - qs = qs.filter(published=True) - - if 'source' in self.request.GET: - qs = qs.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - qs = qs.filter(Q(portal__name=self.request.GET['portal']) | Q(portal=None)) - - qs = qs.annotate(api_geom=Transform("geom", settings.API_SRID)) - return qs - - class TouristicEventList(CustomColumnsMixin, MapEntityList): queryset = TouristicEvent.objects.existing() filterform = TouristicEventFilterSet @@ -308,11 +273,6 @@ class TouristicEventMarkupPublic(TouristicEventDocumentPublicMixin, MarkupPublic pass -class TouristicEventMeta(MetaMixin, DetailView): - model = TouristicEvent - template_name = 'tourism/touristicevent_meta.html' - - class TouristicEventViewSet(GeotrekMapentityViewSet): model = TouristicEvent serializer_class = TouristicEventSerializer @@ -330,173 +290,12 @@ def get_queryset(self): return qs -class TouristicEventAPIViewSet(APIViewSet): - model = TouristicEvent - serializer_class = TouristicEventAPISerializer - geojson_serializer_class = TouristicEventAPIGeojsonSerializer - filter_backends = [DjangoFilterBackend, ] - filterset_class = TouristicEventApiFilterSet - - def get_queryset(self): - qs = TouristicEvent.objects.existing() - qs = qs.filter(published=True) - - if 'source' in self.request.GET: - qs = qs.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - qs = qs.filter(Q(portal__name=self.request.GET['portal']) | Q(portal=None)) - - qs = qs.annotate(api_geom=Transform("geom", settings.API_SRID)) - return qs - - -class InformationDeskViewSet(viewsets.ModelViewSet): - model = InformationDesk - queryset = InformationDesk.objects.all() - serializer_class = InformationDeskGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - qs = super().get_queryset() - if self.kwargs.get('type'): - qs = qs.filter(type_id=self.kwargs['type']) - qs = qs.annotate(api_geom=Transform("geom", settings.API_SRID)) - return qs - - class TrekInformationDeskViewSet(viewsets.ModelViewSet): model = InformationDesk - serializer_class = InformationDeskGeojsonSerializer + serializer_class = TrekInformationDeskGeojsonSerializer permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] def get_queryset(self): pk = self.kwargs['pk'] trek = get_object_or_404(Trek.objects.existing(), pk=pk) return trek.information_desks.all().annotate(api_geom=Transform("geom", settings.API_SRID)) - - -class TrekTouristicContentViewSet(viewsets.ModelViewSet): - model = TouristicContent - serializer_class = TouristicContentAPIGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - trek = get_object_or_404(Trek.objects.existing(), pk=self.kwargs['pk']) - if not trek.is_public(): - raise Http404 - queryset = trek.touristic_contents.filter(published=True) - - if 'categories' in self.request.GET: - queryset = queryset.filter(category__pk__in=self.request.GET['categories'].split(',')) - - if 'source' in self.request.GET: - queryset = queryset.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - queryset = queryset.filter(portal__name=self.request.GET['portal']) - - return queryset.annotate(api_geom=Transform("geom", settings.API_SRID)) - - -class TrekTouristicEventViewSet(viewsets.ModelViewSet): - model = TouristicEvent - serializer_class = TouristicEventAPIGeojsonSerializer - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_queryset(self): - trek = get_object_or_404(Trek.objects.existing(), pk=self.kwargs['pk']) - if not trek.is_public(): - raise Http404 - queryset = trek.touristic_events.filter(published=True) - - if 'source' in self.request.GET: - queryset = queryset.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - queryset = queryset.filter(portal__name=self.request.GET['portal']) - - return queryset.annotate(api_geom=Transform("geom", settings.API_SRID)) - - -if 'geotrek.diving' in settings.INSTALLED_APPS: - class DiveTouristicContentViewSet(viewsets.ModelViewSet): - model = TouristicContent - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_serializer_class(self): - renderer, media_type = self.perform_content_negotiation(self.request) - if getattr(renderer, 'format') == 'geojson': - return TouristicContentAPIGeojsonSerializer - else: - return TouristicContentAPISerializer - - def get_queryset(self): - dive = get_object_or_404(Dive.objects.existing(), pk=self.kwargs['pk']) - queryset = dive.touristic_contents.filter(published=True) - - if 'categories' in self.request.GET: - queryset = queryset.filter(category__pk__in=self.request.GET['categories'].split(',')) - - if 'source' in self.request.GET: - queryset = queryset.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - queryset = queryset.filter(portal__name=self.request.GET['portal']) - - return queryset.annotate(api_geom=Transform("geom", settings.API_SRID)) - - class DiveTouristicEventViewSet(viewsets.ModelViewSet): - model = TouristicEvent - permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] - - def get_serializer_class(self): - renderer, media_type = self.perform_content_negotiation(self.request) - if getattr(renderer, 'format') == 'geojson': - return TouristicEventAPIGeojsonSerializer - else: - return TouristicEventAPISerializer - - def get_queryset(self): - dive = get_object_or_404(Dive.objects.existing(), pk=self.kwargs['pk']) - - queryset = dive.touristic_events.filter(published=True) - if 'source' in self.request.GET: - queryset = queryset.filter(source__name__in=self.request.GET['source'].split(',')) - if 'portal' in self.request.GET: - queryset = queryset.filter(portal__name=self.request.GET['portal']) - return queryset.annotate(api_geom=Transform("geom", settings.API_SRID)) - - -class TouristicCategoryView(APIView): - """ touristiccategories.json generation for API """ - renderer_classes = (JSONRenderer,) - permission_classes = (IsAuthenticatedOrReadOnly,) - - def get(self, request, format=None, lang=None): - response = [] - content_categories = TouristicContentCategory.objects.all() - - if request.GET.get('categories', False): - categories = request.GET['categories'].split(',') - content_categories.filter(pk__in=categories) - - for cont_cat in content_categories: - response.append({'id': cont_cat.prefixed_id, - 'label': cont_cat.label, - 'type1_label': cont_cat.type1_label, - 'type2_label': cont_cat.type2_label, - 'pictogram': os.path.join(settings.MEDIA_URL, cont_cat.pictogram.url), - 'order': cont_cat.order, - 'slug': 'touristic-content'}) - - if request.GET.get('events', False): - response.append({'id': 'E', - 'label': _("Touristic events"), - 'type1_label': "", - 'type2_label': "", - 'pictogram': os.path.join(settings.STATIC_URL, 'tourism', 'touristicevent.svg'), - 'order': None, - 'slug': 'touristic-event'}) - - return Response(response) diff --git a/geotrek/trekking/admin.py b/geotrek/trekking/admin.py index d9fde371df..f9ae2e5dc5 100644 --- a/geotrek/trekking/admin.py +++ b/geotrek/trekking/admin.py @@ -34,7 +34,7 @@ class TrekNetworkAdmin(MergeActionMixin, TabbedTranslationAdmin): class PracticeAdmin(MergeActionMixin, TabbedTranslationAdmin): - list_display = ('name', 'prefixed_id', 'order', 'cirkwi', 'distance', 'pictogram_img') + list_display = ('id', 'name', 'order', 'cirkwi', 'distance', 'pictogram_img') search_fields = ('name',) merge_field = 'network' diff --git a/geotrek/trekking/helpers_sync.py b/geotrek/trekking/helpers_sync.py deleted file mode 100644 index cea9f9ff36..0000000000 --- a/geotrek/trekking/helpers_sync.py +++ /dev/null @@ -1,172 +0,0 @@ -import os -from zipfile import ZipFile - -from django.conf import settings -from django.db.models import Q -from modeltranslation.utils import build_localized_fieldname - -from geotrek.common import views as common_views -from geotrek.trekking import models, views - -if 'geotrek.tourism' in settings.INSTALLED_APPS: - from geotrek.tourism import views as tourism_views -if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - from geotrek.sensitivity import views as sensitivity_views - - -class SyncRando: - def __init__(self, sync): - self.global_sync = sync - - def sync(self, lang): - self.global_sync.sync_geojson(lang, views.POIAPIViewSet, 'pois.geojson', zipfile=self.global_sync.zipfile) - self.global_sync.sync_geojson(lang, views.TrekAPIViewSet, 'treks.geojson', zipfile=self.global_sync.zipfile) - self.global_sync.sync_geojson(lang, views.ServiceAPIViewSet, 'services.geojson', - zipfile=self.global_sync.zipfile) - self.global_sync.sync_static_file(lang, 'trekking/trek.svg') - self.global_sync.sync_static_file(lang, 'trekking/itinerancy.svg') - models_picto = [models.TrekNetwork, models.Practice, models.Accessibility, models.DifficultyLevel, - models.POIType, models.ServiceType, models.Route, models.WebLinkCategory] - self.global_sync.sync_pictograms(lang, models_picto, zipfile=self.global_sync.zipfile) - - treks = models.Trek.objects.existing().order_by('pk') - treks = treks.filter( - Q(**{build_localized_fieldname('published', lang): True}) - | Q(**{'trek_parents__parent__{published_lang}'.format(published_lang=build_localized_fieldname('published', lang)): True, - 'trek_parents__parent__deleted': False}) - ) - if self.global_sync.source: - treks = treks.filter(source__name__in=self.global_sync.source) - - if self.global_sync.portal: - treks = treks.filter(Q(portal__name=self.global_sync.portal) | Q(portal=None)) - - for trek in treks: - self.sync_detail(lang, trek) - - def sync_detail(self, lang, trek): - zipname = os.path.join('zip', 'treks', lang, '{pk}.zip'.format(pk=trek.pk)) - zipfullname = os.path.join(self.global_sync.tmp_root, zipname) - self.global_sync.mkdirs(zipfullname) - self.trek_zipfile = ZipFile(zipfullname, 'w') - - self.global_sync.sync_json(lang, common_views.ParametersView, 'parameters', zipfile=self.global_sync.zipfile) - self.global_sync.sync_json(lang, common_views.ThemeViewSet, 'themes', as_view_args=[{'get': 'list'}], - zipfile=self.global_sync.zipfile) - self.sync_trek_pois(lang, trek, zipfile=self.global_sync.zipfile) - if self.global_sync.with_infrastructures: - self.sync_trek_infrastructures(lang, trek) - if self.global_sync.with_signages: - self.sync_trek_signages(lang, trek) - self.sync_trek_services(lang, trek, zipfile=self.global_sync.zipfile) - self.sync_trek_gpx(lang, trek) - self.sync_trek_kml(lang, trek) - self.global_sync.sync_metas(lang, views.TrekMeta, trek) - self.global_sync.sync_metas(lang, common_views.Meta) - if settings.USE_BOOKLET_PDF: - self.global_sync.sync_pdf(lang, trek, views.TrekDocumentBookletPublic.as_view(model=type(trek))) - else: - self.global_sync.sync_pdf(lang, trek, views.TrekDocumentPublic.as_view(model=type(trek))) - self.global_sync.sync_profile_json(lang, trek) - if not self.global_sync.skip_profile_png: - self.global_sync.sync_profile_png(lang, trek, zipfile=self.global_sync.zipfile) - self.global_sync.sync_dem(lang, trek) - for desk in trek.information_desks.all(): - self.global_sync.sync_media_file(lang, desk.thumbnail, zipfile=self.trek_zipfile) - for poi in trek.published_pois: - self.sync_poi_media(lang, poi) - self.global_sync.sync_media_file(lang, trek.thumbnail, zipfile=self.global_sync.zipfile) - for picture, resized in trek.resized_pictures: - self.global_sync.sync_media_file(lang, resized, zipfile=self.trek_zipfile) - - if self.global_sync.with_events: - self.sync_trek_touristicevents(lang, trek, zipfile=self.global_sync.zipfile) - - if self.global_sync.categories: - self.sync_trek_touristiccontents(lang, trek, zipfile=self.global_sync.zipfile) - - if 'geotrek.sensitivity' in settings.INSTALLED_APPS: - self.sync_trek_sensitiveareas(lang, trek) - - if self.global_sync.verbosity == 2: - self.global_sync.stdout.write("{lang} {name} ...".format(lang=lang, name=zipname), - ending="") - - self.global_sync.close_zip(self.trek_zipfile, zipname) - - def sync_trek_sensitiveareas(self, lang, trek): - params = {'format': 'geojson', 'practices': 'Terrestre'} - - view = sensitivity_views.TrekSensitiveAreaViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'sensitiveareas.geojson') - self.global_sync.sync_view(lang, view, name, params=params, pk=trek.pk) - - def sync_trek_touristiccontents(self, lang, trek, zipfile=None): - params = {'format': 'geojson', - 'categories': ','.join(category for category in self.global_sync.categories)} - self.global_sync.get_params_portal(params) - view = tourism_views.TrekTouristicContentViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'touristiccontents.geojson') - self.global_sync.sync_view(lang, view, name, params=params, zipfile=zipfile, pk=trek.pk) - - for content in trek.touristic_contents.all(): - self.sync_touristiccontent_media(lang, content, zipfile=self.trek_zipfile) - - def sync_trek_touristicevents(self, lang, trek, zipfile=None): - params = {'format': 'geojson'} - self.global_sync.get_params_portal(params) - view = tourism_views.TrekTouristicEventViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'touristicevents.geojson') - self.global_sync.sync_view(lang, view, name, params=params, zipfile=zipfile, pk=trek.pk) - - for event in trek.touristic_events.all(): - self.sync_touristicevent_media(lang, event, zipfile=self.trek_zipfile) - - def sync_touristicevent_media(self, lang, event, zipfile=None): - if event.resized_pictures: - self.global_sync.sync_media_file(lang, event.resized_pictures[0][1], zipfile=zipfile) - for picture, resized in event.resized_pictures[1:]: - self.global_sync.sync_media_file(lang, resized) - - def sync_touristiccontent_media(self, lang, content, zipfile=None): - if content.resized_pictures: - self.global_sync.sync_media_file(lang, content.resized_pictures[0][1], zipfile=zipfile) - for picture, resized in content.resized_pictures[1:]: - self.global_sync.sync_media_file(lang, resized) - - def sync_poi_media(self, lang, poi): - if poi.resized_pictures: - self.global_sync.sync_media_file(lang, poi.resized_pictures[0][1], zipfile=self.trek_zipfile) - for picture, resized in poi.resized_pictures[1:]: - self.global_sync.sync_media_file(lang, resized) - for other_file in poi.files: - self.global_sync.sync_media_file(lang, other_file.attachment_file) - - def sync_trek_infrastructures(self, lang, trek, zipfile=None): - params = {'format': 'geojson'} - view = views.TrekInfrastructureViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'infrastructures.geojson') - self.global_sync.sync_view(lang, view, name, params=params, zipfile=zipfile, pk=trek.pk) - - def sync_trek_signages(self, lang, trek, zipfile=None): - params = {'format': 'geojson'} - view = views.TrekSignageViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'signages.geojson') - self.global_sync.sync_view(lang, view, name, params=params, zipfile=zipfile, pk=trek.pk) - - def sync_trek_pois(self, lang, trek, zipfile=None): - params = {'format': 'geojson'} - view = views.TrekPOIViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'pois.geojson') - self.global_sync.sync_view(lang, view, name, params=params, zipfile=zipfile, pk=trek.pk) - - def sync_trek_services(self, lang, trek, zipfile=None): - view = views.TrekServiceViewSet.as_view({'get': 'list'}) - name = os.path.join('api', lang, 'treks', str(trek.pk), 'services.geojson') - self.global_sync.sync_view(lang, view, name, params={'format': 'geojson'}, zipfile=zipfile, pk=trek.pk) - - def sync_trek_gpx(self, lang, obj): - self.global_sync.sync_object_view(lang, obj, views.TrekGPXDetail.as_view(), '{obj.slug}.gpx') - - def sync_trek_kml(self, lang, obj): - self.global_sync.sync_object_view(lang, obj, views.TrekKMLDetail.as_view(), '{obj.slug}.kml') diff --git a/geotrek/trekking/locale/de/LC_MESSAGES/django.po b/geotrek/trekking/locale/de/LC_MESSAGES/django.po index f6432185c7..90cde33ef3 100644 --- a/geotrek/trekking/locale/de/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: 2015-10-21 11:16+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -336,10 +336,6 @@ msgstr "Eine Route hinzufügen" msgid "Cannot use itself as child trek." msgstr "Es ist nicht möglich, eine Route als eigenes Kind zu benutzen." -#. Translators: This is a slug (without space, accent or special char) -msgid "trek" -msgstr "route" - msgid "Sensitive area" msgstr "Empfindlicher Bereich" @@ -525,16 +521,6 @@ msgid "" "The geometry cannot be converted to a single continuous LineString feature" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "itinerancy" -msgstr "Roaming" - -msgid "Itinerancy" -msgstr "Roaming" - -msgid "Hike" -msgstr "Wander" - msgid "Attached files" msgstr "" @@ -584,6 +570,9 @@ msgstr "" msgid "Photos accessibility" msgstr "" +msgid "Itinerancy" +msgstr "Roaming" + msgid "Child of:" msgstr "Kind von:" @@ -715,9 +704,6 @@ msgstr "Hinzufügen eine POI" msgid "Add a new service" msgstr "Einen Dienst hinzufügen" -msgid "Sync" -msgstr "Sync" - msgid "Add Web link" msgstr "Weblink hinzufügen" diff --git a/geotrek/trekking/locale/en/LC_MESSAGES/django.po b/geotrek/trekking/locale/en/LC_MESSAGES/django.po index e5bf1b73dc..2d0affae22 100644 --- a/geotrek/trekking/locale/en/LC_MESSAGES/django.po +++ b/geotrek/trekking/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-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -329,10 +329,6 @@ msgstr "" msgid "Cannot use itself as child trek." msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "trek" -msgstr "" - msgid "Sensitive area" msgstr "" @@ -510,16 +506,6 @@ msgid "" "The geometry cannot be converted to a single continuous LineString feature" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "itinerancy" -msgstr "" - -msgid "Itinerancy" -msgstr "" - -msgid "Hike" -msgstr "" - msgid "Attached files" msgstr "" @@ -567,6 +553,9 @@ msgstr "" msgid "Photos accessibility" msgstr "" +msgid "Itinerancy" +msgstr "" + msgid "Child of:" msgstr "" @@ -692,9 +681,6 @@ msgstr "" msgid "Add a new service" msgstr "" -msgid "Sync" -msgstr "" - msgid "Add Web link" msgstr "" diff --git a/geotrek/trekking/locale/es/LC_MESSAGES/django.po b/geotrek/trekking/locale/es/LC_MESSAGES/django.po index 9d78247162..7881cff704 100644 --- a/geotrek/trekking/locale/es/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/es/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: 2015-10-21 11:16+0200\n" "Last-Translator: jean-etienne.castagnede@makina-corpus.com\n" "Language-Team: \n" @@ -329,10 +329,6 @@ msgstr "" msgid "Cannot use itself as child trek." msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "trek" -msgstr "" - msgid "Sensitive area" msgstr "" @@ -510,16 +506,6 @@ msgid "" "The geometry cannot be converted to a single continuous LineString feature" msgstr "" -#. Translators: This is a slug (without space, accent or special char) -msgid "itinerancy" -msgstr "" - -msgid "Itinerancy" -msgstr "" - -msgid "Hike" -msgstr "Caminata" - msgid "Attached files" msgstr "" @@ -567,6 +553,9 @@ msgstr "" msgid "Photos accessibility" msgstr "" +msgid "Itinerancy" +msgstr "" + msgid "Child of:" msgstr "" @@ -692,9 +681,6 @@ msgstr "" msgid "Add a new service" msgstr "" -msgid "Sync" -msgstr "" - msgid "Add Web link" msgstr "" diff --git a/geotrek/trekking/locale/fr/LC_MESSAGES/django.po b/geotrek/trekking/locale/fr/LC_MESSAGES/django.po index 5e982152e2..03d855422e 100644 --- a/geotrek/trekking/locale/fr/LC_MESSAGES/django.po +++ b/geotrek/trekking/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+0000\n" "PO-Revision-Date: 2020-09-22 15:57+0000\n" "Last-Translator: Emmanuelle Helly \n" "Language-Team: French - - - - {{ object.name }} - - - - - - {% if object.resized_pictures %} - - - - {% else %} - - - - {% endif %} - - - -

{{ object.name }}

-{{ META_TITLE }} - - diff --git a/geotrek/trekking/templates/trekking/trekking_menuentries_fragment.html b/geotrek/trekking/templates/trekking/trekking_menuentries_fragment.html deleted file mode 100644 index 32db87affa..0000000000 --- a/geotrek/trekking/templates/trekking/trekking_menuentries_fragment.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load i18n %} - -{% if user.is_superuser %} - -{% endif %} \ No newline at end of file diff --git a/geotrek/trekking/tests/test_filters.py b/geotrek/trekking/tests/test_filters.py index aed2e7fe40..9bafc2f8c0 100644 --- a/geotrek/trekking/tests/test_filters.py +++ b/geotrek/trekking/tests/test_filters.py @@ -1,11 +1,12 @@ +from django.test import TestCase + from geotrek.land.tests.test_filters import LandFiltersTest -from geotrek.trekking.filters import TrekFilterSet -from geotrek.trekking.tests.factories import TrekFactory +from geotrek.trekking.filters import TrekFilterSet, POIFilterSet, ServiceFilterSet +from .factories import TrekFactory, POIFactory, ServiceFactory class TrekFilterLandTest(LandFiltersTest): - filterclass = TrekFilterSet def test_land_filters_are_well_setup(self): @@ -16,3 +17,78 @@ def create_pair_of_distinct_path(self): useless_path, seek_path = super().create_pair_of_distinct_path() self.create_pair_of_distinct_topologies(TrekFactory, useless_path, seek_path) return useless_path, seek_path + + +class TrekFilterTest(TestCase): + factory = TrekFactory + filterset = TrekFilterSet + + def test_provider_filter_without_provider(self): + filter_set = TrekFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + trek1 = TrekFactory.create(provider='my_provider1') + trek2 = TrekFactory.create(provider='my_provider2') + + filter_set = TrekFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(trek1, filter_set.qs) + self.assertIn(trek2, filter_set.qs) + + +class POIFilterTest(TestCase): + factory = POIFactory + filterset = POIFilterSet + + def test_provider_filter_without_provider(self): + filter_set = POIFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + poi1 = POIFactory.create(provider='my_provider1') + poi2 = POIFactory.create(provider='my_provider2') + + filter_set = POIFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(poi1, filter_set.qs) + self.assertIn(poi2, filter_set.qs) + + +class ServiceFilterTest(TestCase): + factory = ServiceFactory + filterset = ServiceFilterSet + + def test_provider_filter_without_provider(self): + filter_set = ServiceFilterSet(data={}) + filter_form = filter_set.form + + self.assertTrue(filter_form.is_valid()) + self.assertEqual(0, filter_set.qs.count()) + + def test_provider_filter_with_providers(self): + service1 = ServiceFactory.create(provider='my_provider1') + service2 = ServiceFactory.create(provider='my_provider2') + + filter_set = ServiceFilterSet() + filter_form = filter_set.form + + self.assertIn('', filter_form.as_p()) + self.assertIn('', filter_form.as_p()) + + self.assertIn(service1, filter_set.qs) + self.assertIn(service2, filter_set.qs) diff --git a/geotrek/trekking/tests/test_helpers_sync.py b/geotrek/trekking/tests/test_helpers_sync.py deleted file mode 100644 index 57717b89ae..0000000000 --- a/geotrek/trekking/tests/test_helpers_sync.py +++ /dev/null @@ -1,143 +0,0 @@ -import os -from unittest.mock import patch - -from io import StringIO - -from django.test import TestCase -from django.test.utils import override_settings -from django.conf import settings - -from geotrek.common.tests.factories import FakeSyncCommand, RecordSourceFactory, TargetPortalFactory, AttachmentFactory -from geotrek.common.utils.testdata import get_dummy_uploaded_image, get_dummy_uploaded_file -from geotrek.trekking.tests.factories import TrekFactory, TrekWithPublishedPOIsFactory -from geotrek.trekking import models as trek_models -from geotrek.tourism.tests.factories import InformationDeskFactory, TouristicContentFactory, TouristicEventFactory - -from geotrek.trekking.helpers_sync import SyncRando - - -@patch('geotrek.trekking.models.Trek.prepare_map_image') -class SyncRandoTestCase(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.trek = TrekWithPublishedPOIsFactory.create(published=True) - cls.information_desks = InformationDeskFactory.create() - cls.trek.information_desks.add(cls.information_desks) - cls.attachment = AttachmentFactory.create(content_object=cls.trek, - attachment_file=get_dummy_uploaded_image()) - - cls.source_a = RecordSourceFactory() - cls.source_b = RecordSourceFactory() - - cls.portal_a = TargetPortalFactory() - cls.portal_b = TargetPortalFactory() - cls.trek_fr = TrekFactory.create(published_fr=True, sources=(cls.source_b,)) - cls.trek_sb = TrekFactory.create(sources=(cls.source_b,), - published=True) - - cls.trek_sb_pa = TrekFactory.create(sources=(cls.source_b,), portals=(cls.portal_a,), - published=True) - - cls.touristic_content = TouristicContentFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.touristic_event = TouristicEventFactory( - geom='SRID=%s;POINT(700001 6600001)' % settings.SRID, published=True) - cls.attachment_touristic_content = AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - cls.attachment_touristic_event = AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.touristic_content, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.touristic_event, - attachment_file=get_dummy_uploaded_image()) - cls.poi = trek_models.POI.objects.first() - cls.attachment_poi_image = AttachmentFactory.create(content_object=cls.poi, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.poi, - attachment_file=get_dummy_uploaded_image()) - AttachmentFactory.create(content_object=cls.poi, - attachment_file=get_dummy_uploaded_file()) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_detail_no_portal_no_source(self, stdout, mock_prepare): - command = FakeSyncCommand() - synchro = SyncRando(command) - synchro.sync_detail('fr', self.trek) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), '%s.pdf' % self.trek.slug))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'pois.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'dem.json'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'profile.png'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'touristiccontents.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'touristicevents.geojson'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_detail_no_portal_no_source_skip_everything(self, stdout, mock_prepare): - command = FakeSyncCommand(skip_dem=True, skip_pdf=True, skip_profile_png=True) - synchro = SyncRando(command) - synchro.sync_detail('fr', self.trek) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'pois.geojson'))) - self.assertFalse(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), '%s.pdf' % self.trek.slug))) - self.assertFalse(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'dem.json'))) - self.assertFalse(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'profile.png'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_detail_booklet(self, stdout, mock_prepare): - command = FakeSyncCommand(skip_dem=True, skip_profile_png=True) - synchro = SyncRando(command) - with override_settings(USE_BOOKLET_PDF=False): - synchro.sync_detail('en', self.trek) - first_file = os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'treks', str(self.trek.pk), - '%s.pdf' % self.trek.slug) - size_first = os.stat(first_file).st_size - with override_settings(USE_BOOKLET_PDF=True): - synchro.sync_detail('en', self.trek) - second_file = os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'en', 'treks', str(self.trek.pk), - '%s.pdf' % self.trek.slug) - size_second = os.stat(second_file).st_size - self.assertLess(size_first, size_second) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_detail_portal_source(self, stdout, mock_prepare): - command = FakeSyncCommand(portal=self.portal_b.name, source=[self.source_b.name]) - synchro = SyncRando(command) - synchro.sync_detail('fr', self.trek) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), '%s.pdf' % self.trek.slug))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'pois.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'touristiccontents.geojson'))) - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'api', 'fr', 'treks', - str(self.trek.pk), 'touristicevents.geojson'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_language(self, stdout, mock_prepare): - def side_effect_sync(lang, trek): - self.assertEqual(trek, self.trek_fr) - command = FakeSyncCommand() - synchro = SyncRando(command) - with patch('geotrek.trekking.helpers_sync.SyncRando.sync_detail', side_effect=side_effect_sync): - synchro.sync('fr') - self.assertTrue(os.path.exists(os.path.join(settings.VAR_DIR, command.tmp_root, 'static', 'trekking', 'trek.svg'))) - - @patch('sys.stdout', new_callable=StringIO) - def test_sync_language_portal_source(self, stdout, mock_prepare): - def side_effect_sync(lang, trek): - self.assertEqual(trek, self.trek_fr) - command = FakeSyncCommand(portal=self.portal_a.name, source=[self.source_b.name]) - synchro = SyncRando(command) - with patch('geotrek.trekking.helpers_sync.SyncRando.sync_detail', side_effect=side_effect_sync) as mock_trek: - synchro.sync('fr') - self.assertEqual(len(mock_trek.call_args_list), 1) - mock_trek.assert_called_with('fr', self.trek_fr) diff --git a/geotrek/trekking/tests/test_models.py b/geotrek/trekking/tests/test_models.py index ed1355e9a3..c85272a1d9 100644 --- a/geotrek/trekking/tests/test_models.py +++ b/geotrek/trekking/tests/test_models.py @@ -1,3 +1,6 @@ +import datetime +import os +from tempfile import TemporaryDirectory from unittest import skipIf from bs4 import BeautifulSoup @@ -9,7 +12,9 @@ from django.core.exceptions import ValidationError from django.test import TestCase from django.test.utils import override_settings -from geotrek.common.tests.factories import LabelFactory +from easy_thumbnails.files import ThumbnailFile + +from geotrek.common.tests.factories import LabelFactory, AttachmentImageFactory, AttachmentPictoSVGFactory from mapentity.middleware import clear_internal_user_cache from geotrek.common.tests import TranslationResetMixin @@ -27,6 +32,12 @@ class TrekTest(TranslationResetMixin, TestCase): + def test_is_public_if_parent_published(self): + t = TrekFactory.create(published=False) + parent = TrekFactory.create(published=True) + OrderedTrekChild.objects.create(parent=parent, child=t) + self.assertTrue(t.is_public()) + def test_is_publishable(self): t = TrekFactory.create() t.geom = LineString((0, 0), (1, 1)) @@ -97,7 +108,7 @@ def test_published_langs_without_published_by_lang_not_published(self): def test_kml_coordinates_should_be_3d(self): trek = TrekWithPOIsFactory.create() kml = trek.kml() - parsed = BeautifulSoup(kml, 'lxml') + parsed = BeautifulSoup(kml, features='xml') for placemark in parsed.findAll('placemark'): coordinates = placemark.find('coordinates') tuples = [s.split(',') for s in coordinates.string.split(' ')] @@ -158,6 +169,35 @@ def test_trek_itself_as_parent(self): "Cannot use itself as child trek.", trek1.full_clean) + @override_settings(MEDIA_ROOT=TemporaryDirectory().name) + def test_pictures_print_thumbnail_correct_picture(self): + trek = TrekFactory() + AttachmentImageFactory.create_batch(5, content_object=trek) + self.assertEqual(trek.pictures.count(), 5) + self.assertEqual(len(os.listdir(os.path.dirname(trek.attachments.first().attachment_file.path))), 5) + self.assertTrue(isinstance(trek.picture_print, ThumbnailFile)) + + @override_settings(MEDIA_ROOT=TemporaryDirectory().name) + def test_pictures_print_thumbnail_wrong_picture(self): + trek = TrekFactory() + error_image_attachment = AttachmentPictoSVGFactory(content_object=trek) + os.unlink(error_image_attachment.attachment_file.path) + self.assertIsNone(trek.picture_print) + + @override_settings(MEDIA_ROOT=TemporaryDirectory().name) + def test_pictures_print_thumbnail_no_picture(self): + trek = TrekFactory() + self.assertEqual(trek.pictures.count(), 0) + self.assertIsNone(trek.picture_print, ThumbnailFile) + + @override_settings(MEDIA_ROOT=TemporaryDirectory().name) + def test_thumbnail(self): + trek = TrekFactory() + AttachmentImageFactory(content_object=trek) + self.assertTrue(isinstance(trek.thumbnail, ThumbnailFile)) + self.assertIsNotNone(trek.thumbnail) + self.assertIn(trek.thumbnail.name, trek.thumbnail_display) + class TrekPublicationDateTest(TranslationResetMixin, TestCase): def setUp(self): @@ -178,7 +218,6 @@ def test_becomes_null_when_unpublished(self): self.assertIsNone(self.trek.publication_date) def test_date_is_not_updated_when_saved_again(self): - import datetime self.test_takes_current_date_when_published_becomes_true() old_date = datetime.date(2003, 8, 6) self.trek.publication_date = old_date @@ -510,7 +549,6 @@ def test_cascading_from_practice(self): class TrekLabelsTestCase(TestCase): - def setUp(self): self.trek = TrekFactory() self.published_label = LabelFactory(published=True) diff --git a/geotrek/trekking/tests/test_views.py b/geotrek/trekking/tests/test_views.py index c255cd4854..4a52962dfb 100755 --- a/geotrek/trekking/tests/test_views.py +++ b/geotrek/trekking/tests/test_views.py @@ -1,5 +1,4 @@ import csv -import hashlib import os from collections import OrderedDict from io import StringIO @@ -23,32 +22,25 @@ from geotrek.authent.tests.base import AuthentFixturesTest from geotrek.authent.tests.factories import TrekkingManagerFactory, StructureFactory, UserProfileFactory -from geotrek.common.templatetags import geotrek_tags -from geotrek.common.tests import CommonTest, CommonLiveTest, TranslationResetMixin, GeotrekAPITestCase -from geotrek.common.tests.factories import (AttachmentFactory, ThemeFactory, LabelFactory, RecordSourceFactory, - TargetPortalFactory) +from geotrek.common.tests import CommonTest, CommonLiveTest, TranslationResetMixin +from geotrek.common.tests.factories import AttachmentFactory, ThemeFactory from geotrek.common.utils.testdata import get_dummy_uploaded_image from geotrek.core.tests.factories import PathFactory -from geotrek.infrastructure.models import Infrastructure -from geotrek.infrastructure.tests.factories import InfrastructureFactory -from geotrek.signage.models import Signage -from geotrek.signage.tests.factories import SignageFactory from geotrek.tourism.tests import factories as tourism_factories # Make sur to register Trek model from geotrek.trekking import urls # NOQA from geotrek.trekking import views as trekking_views -from geotrek.trekking.filters import TrekFilterSet, POIFilterSet, ServiceFilterSet from geotrek.zoning.tests.factories import DistrictFactory, CityFactory from .base import TrekkingManagerTest from .factories import (POIFactory, POITypeFactory, TrekFactory, TrekWithPOIsFactory, TrekNetworkFactory, WebLinkFactory, AccessibilityFactory, - TrekRelationshipFactory, ServiceFactory, ServiceTypeFactory, + ServiceFactory, ServiceTypeFactory, TrekWithServicesFactory, TrekWithInfrastructuresFactory, TrekWithSignagesFactory, PracticeFactory) -from ..models import POI, Trek, Service, OrderedTrekChild +from ..models import POI, Trek, Service -class POIViewsTest(GeotrekAPITestCase, CommonTest): +class POIViewsTest(CommonTest): model = POI modelfactory = POIFactory userfactory = TrekkingManagerFactory @@ -63,40 +55,8 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'areas': [], - 'cities': [], - 'description': '

Description

', - 'districts': [], - 'filelist_url': '/paperclip/get/trekking/poi/{}/'.format(self.obj.pk), - 'files': [], - 'map_image_url': '/image/poi-{}.png'.format(self.obj.pk), - 'max_elevation': 0, - 'min_elevation': 0, - 'name': 'POI', - 'pictures': [], - 'printable': '/api/en/pois/{}/poi.pdf'.format(self.obj.pk), - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False}, - ], - 'slug': 'poi', - 'structure': {'id': self.obj.structure.pk, 'name': 'My structure'}, - 'thumbnail': None, - 'type': { - 'id': self.obj.type.pk, - 'label': 'POI type', - 'pictogram': '/media/upload/poi-type.png', - }, - 'videos': [], + 'name': self.obj.name, + 'published': self.obj.published } def get_expected_datatables_attrs(self): @@ -225,7 +185,7 @@ def test_pois_on_treks_not_public_anonymous(self): self.assertEqual(response.status_code, 404) -class TrekViewsTest(GeotrekAPITestCase, CommonTest): +class TrekViewsTest(CommonTest): model = Trek modelfactory = TrekFactory userfactory = TrekkingManagerFactory @@ -241,117 +201,8 @@ def get_expected_geojson_geom(self): def get_expected_geojson_attrs(self): return { 'id': self.obj.pk, - 'name': self.obj.name - } - - def get_expected_json_attrs(self): - return { - 'access': '

Access

', - 'accessibilities': [], - 'accessibility_advice': '

Accessibility advice

', - 'accessibility_covering': '

Accessibility covering

', - 'accessibility_exposure': '

Accessibility exposure

', - 'accessibility_level': {'id': self.obj.accessibility_level.pk, 'label': 'Easy'}, - 'accessibility_signage': '

Accessibility signage

', - 'accessibility_slope': '

Accessibility slope

', - 'accessibility_width': '

Accessibility width

', - 'advice': '

Advice

', - 'advised_parking': '

Advised parking

', - 'altimetric_profile': '/api/en/treks/{}/profile.json'.format(self.obj.pk), - 'ambiance': '

Ambiance

', - 'areas': [], - 'arrival': 'Arrival', - 'ascent': 0, - 'category': { - 'id': 'T', - 'label': 'Hike', - 'order': 1, - 'pictogram': '/static/trekking/trek.svg', - 'slug': 'trek', - 'type2_label': 'Accessibility type', - }, - 'children': [], - 'cities': [], - 'departure': 'Departure', - 'descent': 0, - 'description': '

Description

', - 'description_teaser': '

Description teaser

', - 'difficulty': { - 'id': self.obj.difficulty.pk, - 'label': 'Difficulty', - 'pictogram': '/media/upload/difficulty.png', - }, - 'accessibility_infrastructure': '

Accessibility infrastructure

', - 'districts': [], - 'dives': [], - 'duration': 1.5, - 'duration_pretty': '1 h 30', - 'elevation_area_url': '/api/en/treks/{}/dem.json'.format(self.obj.pk), - 'elevation_svg_url': '/api/en/treks/{}/profile.svg'.format(self.obj.pk), - 'gear': '

Gear

', - 'filelist_url': '/paperclip/get/trekking/trek/{}/'.format(self.obj.pk), - 'files': [], - 'gpx': '/api/en/treks/{}/trek.gpx'.format(self.obj.pk), - 'information_desks': [], - 'labels': [], - 'kml': '/api/en/treks/{}/trek.kml'.format(self.obj.pk), - 'map_image_url': '/image/trek-{}-en.png'.format(self.obj.pk), - 'max_elevation': 0, - 'min_elevation': 0, - 'name': 'Trek', - 'networks': [], - 'next': {}, - 'parents': [], - 'parking_location': [-1.3630753, -5.9838497], - 'pictures': [], - 'points_reference': None, - 'portal': [], - 'practice': { - 'id': self.obj.practice.pk, - 'label': 'Usage', - 'pictogram': '/media/upload/practice.png', - }, - 'previous': {}, - 'printable': '/api/en/treks/{}/trek.pdf'.format(self.obj.pk), - 'public_transport': '

Public transport

', - 'publication_date': '2020-03-17', - 'published': True, - 'published_status': [ - {'lang': 'en', 'language': 'English', 'status': True}, - {'lang': 'es', 'language': 'Spanish', 'status': False}, - {'lang': 'fr', 'language': 'French', 'status': False}, - {'lang': 'it', 'language': 'Italian', 'status': False} - ], - 'relationships': [], - 'route': { - 'id': self.obj.route.pk, - 'label': 'Route', - 'pictogram': '/media/upload/routes.png', - }, - 'slope': 0.0, - 'slug': 'trek', - 'source': [], - 'structure': { - 'id': self.obj.structure.pk, - 'name': 'My structure', - }, - 'themes': [], - 'thumbnail': None, - 'touristic_contents': [], - 'touristic_events': [], - 'treks': [], - 'type2': [], - 'usages': [{ - 'id': self.obj.practice.pk, - 'label': 'Usage', - 'pictogram': '/media/upload/practice.png' - }], - 'videos': [], - 'web_links': [], - 'reservation_id': 'XXXXXXXXX', - 'reservation_system': self.obj.reservation_system.name, - 'ratings': [], - 'ratings_description': '', + 'name': self.obj.name, + 'published': self.obj.published } def get_expected_datatables_attrs(self): @@ -606,7 +457,6 @@ def test_trek_pois_geojson(self): self.assertEqual(response.status_code, 200) poislayer = response.json() poifeature = poislayer['features'][0] - self.assertTrue('thumbnail' in poifeature['properties']) self.assertEqual(len(poislayer['features']), 1) self.assertEqual(poifeature['properties']['name'], poi.name) @@ -623,50 +473,6 @@ def test_pois_geojson(self): self.assertNotEqual(poi.thumbnail, None) self.assertEqual(POI.objects.filter(published=True).count(), 1) - response = self.client.get('/api/en/pois.geojson') - self.assertEqual(response.status_code, 200) - poislayer = response.json() - poifeature = poislayer['features'][0] - self.assertTrue('thumbnail' in poifeature['properties']) - self.assertEqual(len(poislayer['features']), 1) - self.assertEqual(poifeature['properties']['name'], poi.name) - - def test_infrastructures_geojson(self): - infra = InfrastructureFactory.create() - infra2 = InfrastructureFactory.create() - self.assertEqual(Infrastructure.objects.count(), 2) - infra.published = True - infra2.published = False - infra.save() - infra2.save() - - self.assertEqual(Infrastructure.objects.filter(published=True).count(), 1) - - response = self.client.get('/api/en/infrastructures.geojson') - self.assertEqual(response.status_code, 200) - infraslayer = response.json() - infrafeature = infraslayer['features'][0] - self.assertEqual(len(infraslayer['features']), 1) - self.assertEqual(infrafeature['properties']['name'], infra.name) - - def test_signages_geojson(self): - signa = SignageFactory.create() - signa2 = SignageFactory.create() - self.assertEqual(Signage.objects.count(), 2) - signa.published = True - signa2.published = False - signa.save() - signa2.save() - - self.assertEqual(Signage.objects.filter(published=True).count(), 1) - - response = self.client.get('/api/en/signages.geojson') - self.assertEqual(response.status_code, 200) - poislayer = response.json() - poifeature = poislayer['features'][0] - self.assertEqual(len(poislayer['features']), 1) - self.assertEqual(poifeature['properties']['name'], signa.name) - def test_services_geojson(self): trek = TrekWithServicesFactory.create(published=True) self.assertEqual(len(trek.services), 2) @@ -777,350 +583,6 @@ def test_not_published_elevation_area_json(self): self.assertEqual(response.status_code, 403) -class TrekJSONSetUp(TrekkingManagerTest): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - polygon = 'SRID=%s;MULTIPOLYGON(((0 0, 0 3, 3 3, 3 0, 0 0)))' % settings.SRID - cls.city = CityFactory(geom=polygon) - cls.district = DistrictFactory(geom=polygon) - - trek_args = {'name': 'Step 2', - 'points_reference': MultiPoint([Point(0, 0), Point(1, 1)], srid=settings.SRID), - 'parking_location': Point(0, 0, srid=settings.SRID)} - if settings.TREKKING_TOPOLOGY_ENABLED: - path1 = PathFactory.create(geom='SRID=%s;LINESTRING(0 0, 1 0)' % settings.SRID) - cls.trek = TrekFactory.create( - paths=[path1], - **trek_args - ) - else: - cls.trek = TrekFactory.create( - geom='SRID=%s;LINESTRING(0 0, 1 0)' % settings.SRID, - **trek_args - ) - cls.attachment = AttachmentFactory.create(content_object=cls.trek, - attachment_file=get_dummy_uploaded_image()) - - cls.information_desk = tourism_factories.InformationDeskFactory.create() - cls.trek.information_desks.add(cls.information_desk) - - cls.theme = ThemeFactory.create() - cls.trek.themes.add(cls.theme) - - cls.accessibility = AccessibilityFactory.create() - cls.trek.accessibilities.add(cls.accessibility) - - cls.network = TrekNetworkFactory.create() - cls.trek.networks.add(cls.network) - - cls.weblink = WebLinkFactory.create() - cls.trek.web_links.add(cls.weblink) - - cls.label = LabelFactory.create() - cls.trek.labels.add(cls.label) - - cls.source = RecordSourceFactory.create() - cls.trek.source.add(cls.source) - - cls.portal = TargetPortalFactory.create() - cls.trek.portal.add(cls.portal) - trek_b_args = {'published': True} - if settings.TREKKING_TOPOLOGY_ENABLED: - path2 = PathFactory.create(geom='SRID=%s;LINESTRING(0 1, 1 1)' % settings.SRID) - cls.trek_b = TrekFactory.create(paths=[path2], **trek_b_args) - TrekFactory(paths=[path2], published=False) # not published - cls.trek3 = TrekFactory(paths=[path2], published=True) # deleted - cls.trek3.delete() - TrekFactory(paths=[PathFactory.create(geom='SRID=%s;LINESTRING(0 2000, 1 2000)' % settings.SRID)], - published=True) # too far - else: - cls.trek_b = TrekFactory.create(geom='SRID=%s;LINESTRING(0 1, 1 1)' % settings.SRID, **trek_b_args) - TrekFactory(geom='SRID=%s;LINESTRING(0 1, 1 1)' % settings.SRID, published=False) - trek3 = TrekFactory(geom='SRID=%s;LINESTRING(0 1, 1 1)' % settings.SRID, published=True) - trek3.delete() - TrekFactory(geom='SRID=%s;LINESTRING(0 2000, 1 2000)' % settings.SRID, published=True) # too far - - TrekRelationshipFactory.create(has_common_departure=True, - has_common_edge=False, - is_circuit_step=True, - trek_a=cls.trek, - trek_b=cls.trek_b) - - cls.touristic_content = tourism_factories.TouristicContentFactory(geom='SRID=%s;POINT(1 1)' % settings.SRID, - published=True) - tourism_factories.TouristicContentFactory(geom='SRID=%s;POINT(1 1)' % settings.SRID, - published=False) # not published - tourism_factories.TouristicContentFactory(geom='SRID=%s;POINT(1 1)' % settings.SRID, - published=True).delete() # deleted - tourism_factories.TouristicContentFactory(geom='SRID=%s;POINT(1000 1000)' % settings.SRID, - published=True) # too far - cls.touristic_event = tourism_factories.TouristicEventFactory(geom='SRID=%s;POINT(2 2)' % settings.SRID, - published=True) - tourism_factories.TouristicEventFactory(geom='SRID=%s;POINT(2 2)' % settings.SRID, - published=False) # not published - tourism_factories.TouristicEventFactory(geom='SRID=%s;POINT(2 2)' % settings.SRID, - published=True).delete() # deleted - tourism_factories.TouristicEventFactory(geom='SRID=%s;POINT(2000 2000)' % settings.SRID, - published=True) # too far - - cls.parent = TrekFactory.create(published=True, name='Parent') - cls.child1 = TrekFactory.create(published=False, name='Child 1') - cls.child2 = TrekFactory.create(published=True, name='Child 2') - cls.sibling = TrekFactory.create(published=True, name='Sibling') - OrderedTrekChild(parent=cls.parent, child=cls.trek, order=0).save() - OrderedTrekChild(parent=cls.trek, child=cls.child1, order=3).save() - OrderedTrekChild(parent=cls.trek, child=cls.child2, order=2).save() - OrderedTrekChild(parent=cls.parent, child=cls.sibling, order=1).save() - cls.pk = cls.trek.pk - - @override_settings(THUMBNAIL_COPYRIGHT_FORMAT="{title} {author}") - def setUp(self): - self.login() - url = '/api/en/treks/{pk}.json'.format(pk=self.pk) - self.response = self.client.get(url) - self.result = self.response.json() - - -@override_settings(SPLIT_TREKS_CATEGORIES_BY_PRACTICE=True) -class TrekPracticeTest(TrekJSONSetUp): - def test_touristic_contents_practice(self): - self.assertEqual(len(self.result['touristic_contents']), 1) - self.assertDictEqual(self.result['touristic_contents'][0], { - 'id': self.touristic_content.pk, - 'category_id': self.touristic_content.prefixed_category_id}) - - -class TrekJSONDetailTest(TrekJSONSetUp): - """ Since we migrated some code to Django REST Framework, we should test - the migration extensively. Geotrek-rando mainly relies on this view. - """ - - def test_related_urls(self): - self.assertEqual(self.result['elevation_area_url'], - '/api/en/treks/{pk}/dem.json'.format(pk=self.pk)) - self.assertEqual(self.result['map_image_url'], - '/image/trek-%s-en.png' % self.pk) - self.assertEqual(self.result['altimetric_profile'], - '/api/en/treks/{pk}/profile.json'.format(pk=self.pk)) - self.assertEqual(self.result['filelist_url'], - '/paperclip/get/trekking/trek/%s/' % self.pk) - self.assertEqual(self.result['gpx'], - '/api/en/treks/{pk}/{slug}.gpx'.format(pk=self.pk, slug=self.trek.slug)) - self.assertEqual(self.result['kml'], - '/api/en/treks/{pk}/{slug}.kml'.format(pk=self.pk, slug=self.trek.slug)) - self.assertEqual(self.result['printable'], - '/api/en/treks/{pk}/{slug}.pdf'.format(pk=self.pk, slug=self.trek.slug)) - - def test_thumbnail(self): - self.assertEqual(self.result['thumbnail'], - os.path.join(settings.MEDIA_URL, - self.attachment.attachment_file.name) + '.120x120_q85_crop.png') - - def test_published_status(self): - self.assertDictEqual(self.result['published_status'][0], - {'lang': 'en', 'status': True, 'language': 'English'}) - - @override_settings(THUMBNAIL_COPYRIGHT_FORMAT="{title} {author}") - def test_pictures(self): - url = '{url}.800x800_q85_watermark-{id}.png'.format( - url=self.attachment.attachment_file.url, - id=hashlib.md5( - settings.THUMBNAIL_COPYRIGHT_FORMAT.format( - author=self.attachment.author, - title=self.attachment.title, - legend=self.attachment.legend).encode()).hexdigest()) - self.assertDictEqual(self.result['pictures'][0], - {'url': url, - 'title': self.attachment.title, - 'legend': self.attachment.legend, - 'author': self.attachment.author}) - response = self.client.get(url) - self.assertEqual(response.status_code, 200) - - def test_networks(self): - self.assertDictEqual(self.result['networks'][0], - {"id": self.network.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.network.pictogram.name), - "name": self.network.network}) - - def test_practice_not_none(self): - self.assertDictEqual(self.result['practice'], - {"id": self.trek.practice.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.trek.practice.pictogram.name), - "label": self.trek.practice.name}) - - def test_usages(self): # Rando v1 compat - self.assertDictEqual(self.result['usages'][0], - {"id": self.trek.practice.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.trek.practice.pictogram.name), - "label": self.trek.practice.name}) - - def test_accessibilities(self): - self.assertDictEqual(self.result['accessibilities'][0], - {"id": self.accessibility.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.accessibility.pictogram.name), - "label": self.accessibility.name}) - - def test_themes(self): - self.assertDictEqual(self.result['themes'][0], - {"id": self.theme.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.theme.pictogram.name), - "label": self.theme.label}) - - def test_labels(self): - self.assertDictEqual(self.result['labels'][0], - {"id": self.label.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.label.pictogram.name), - "name": self.label.name, - "advice": self.label.advice, - "filter_rando": self.label.filter}) - - def test_weblinks(self): - self.assertDictEqual(self.result['web_links'][0], - {"id": self.weblink.id, - "url": self.weblink.url, - "name": self.weblink.name, - "category": { - "id": self.weblink.category.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.weblink.category.pictogram.name), - "label": self.weblink.category.label} - }) - - def test_route_not_none(self): - self.assertDictEqual(self.result['route'], - {"id": self.trek.route.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.trek.route.pictogram.name), - "label": self.trek.route.route}) - - def test_difficulty_not_none(self): - self.assertDictEqual(self.result['difficulty'], - {"id": self.trek.difficulty.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.trek.difficulty.pictogram.name), - "label": self.trek.difficulty.difficulty}) - - def test_information_desks(self): - desk_type = self.information_desk.type - self.maxDiff = None - self.assertDictEqual(self.result['information_desks'][0], - {'accessibility': self.information_desk.accessibility, - 'description': self.information_desk.description, - 'email': self.information_desk.email, - 'label_accessibility': { - 'id': self.information_desk.label_accessibility.pk, - 'pictogram': self.information_desk.label_accessibility.pictogram.url, - 'label': self.information_desk.label_accessibility.label, - }, - 'latitude': self.information_desk.latitude, - 'longitude': self.information_desk.longitude, - 'name': self.information_desk.name, - 'phone': self.information_desk.phone, - 'photo_url': self.information_desk.photo_url, - 'postal_code': self.information_desk.postal_code, - 'street': self.information_desk.street, - 'municipality': self.information_desk.municipality, - 'website': self.information_desk.website, - 'type': { - 'id': desk_type.id, - 'pictogram': desk_type.pictogram.url, - 'label': desk_type.label}}) - - def test_relationships(self): - self.assertDictEqual(self.result['relationships'][0], - {'published': self.trek_b.published, - 'has_common_departure': True, - 'has_common_edge': False, - 'is_circuit_step': True, - 'trek': {'pk': self.trek_b.pk, - 'id': self.trek_b.id, - 'slug': self.trek_b.slug, - 'category_slug': 'trek', - 'name': self.trek_b.name}}) - - def test_parking_location_in_wgs84(self): - parking_location = self.result['parking_location'] - self.assertAlmostEqual(parking_location[0], -1.3630812101179008) - - def test_points_reference_are_exported_in_wgs84(self): - geojson = self.result['points_reference'] - self.assertEqual(geojson['type'], 'MultiPoint') - self.assertAlmostEqual(geojson['coordinates'][0][0], -1.363081210117901) - - def test_touristic_contents(self): - self.assertEqual(len(self.result['touristic_contents']), 1) - self.assertDictEqual(self.result['touristic_contents'][0], { - 'id': self.touristic_content.pk, - 'category_id': self.touristic_content.prefixed_category_id}) - - def test_touristic_events(self): - self.assertEqual(len(self.result['touristic_events']), 1) - self.assertDictEqual(self.result['touristic_events'][0], { - 'id': self.touristic_event.pk, - 'category_id': self.touristic_event.prefixed_category_id}) - - def test_close_treks(self): - self.assertEqual(len(self.result['treks']), 1) - self.assertDictEqual(self.result['treks'][0], { - 'id': self.trek_b.pk, - 'category_id': self.trek_b.prefixed_category_id}) - - def test_type2(self): - self.assertDictEqual(self.result['type2'][0], - {"id": self.accessibility.id, - "pictogram": os.path.join(settings.MEDIA_URL, self.accessibility.pictogram.name), - "name": self.accessibility.name}) - - def test_category(self): - self.assertDictEqual(self.result['category'], - {"id": 'T', - "order": 1, - "label": "Hike", - "slug": "trek", - "type2_label": "Accessibility type", - "pictogram": "/static/trekking/trek.svg"}) - - def test_sources(self): - self.assertDictEqual(self.result['source'][0], { - 'name': self.source.name, - 'website': self.source.website, - "pictogram": os.path.join(settings.MEDIA_URL, self.source.pictogram.name)}) - - def portals(self): - self.assertDictEqual(self.result['portal'][0], { - 'name': self.portal.name, - 'website': self.portal.website, }) - - def test_children(self): - self.assertEqual(self.result['children'], [self.child2.pk, self.child1.pk]) - - def test_parents(self): - self.assertEqual(self.result['parents'], [self.parent.pk]) - - def test_previous(self): - self.assertDictEqual(self.result['previous'], - {"%s" % self.parent.pk: None}) - - def test_next(self): - self.assertDictEqual(self.result['next'], - {"%s" % self.parent.pk: self.sibling.pk}) - - def test_picture_print(self): - self.assertIn(self.attachment.attachment_file.name, self.trek.picture_print.name) - self.assertIn('.1000x500_q85_crop-smart.png', self.trek.picture_print.name) - - def test_thumbnail_display(self): - self.assertIn('' - % self.attachment.attachment_file.name, self.trek.thumbnail_display) - - def test_thumbnail_csv_display(self): - self.assertIn('%s.120x120_q85_crop.png' - % self.attachment.attachment_file.name, self.trek.thumbnail_csv_display) - - def test_reservation(self): - self.assertEqual(self.result['reservation_system'], self.trek.reservation_system.name) - self.assertEqual(self.result['reservation_id'], 'XXXXXXXXX') - - class TrekPointsReferenceTest(TrekkingManagerTest): @classmethod def setUpTestData(cls): @@ -1174,7 +636,7 @@ def setUp(self): self.login() url = '/api/it/treks/{pk}/slug.gpx'.format(pk=self.trek.pk) self.response = self.client.get(url) - self.parsed = BeautifulSoup(self.response.content, 'lxml') + self.parsed = BeautifulSoup(self.response.content, features='xml') def tearDown(self): translation.deactivate() @@ -1227,18 +689,6 @@ def tearDown(self): translation.deactivate() self.client.logout() - def test_json_translation(self): - for lang, expected in [('fr', self.trek.name_fr), - ('it', 404)]: - url = '/api/{lang}/treks/{pk}.json'.format(lang=lang, pk=self.trek.pk) - response = self.client.get(url) - if expected == 404: - self.assertEqual(response.status_code, 404) - else: - self.assertEqual(response.status_code, 200) - obj = response.json() - self.assertEqual(obj['name'], expected) - def test_geojson_translation(self): for lang, expected in [('fr', self.trek.name_fr), ('it', self.trek.name_it)]: @@ -1286,24 +736,6 @@ def test_poi_geojson_translation(self): self.client.logout() # Django 1.6 keeps language in session -class TemplateTagsTest(TestCase): - def test_duration(self): - self.assertEqual("15 min", geotrek_tags.duration(0.25)) - self.assertEqual("30 min", geotrek_tags.duration(0.5)) - self.assertEqual("1 h", geotrek_tags.duration(1)) - self.assertEqual("1 h 45", geotrek_tags.duration(1.75)) - self.assertEqual("3 h 30", geotrek_tags.duration(3.5)) - self.assertEqual("4 h", geotrek_tags.duration(4)) - self.assertEqual("6 h", geotrek_tags.duration(6)) - self.assertEqual("10 h", geotrek_tags.duration(10)) - self.assertEqual("1 day", geotrek_tags.duration(24)) - self.assertEqual("2 days", geotrek_tags.duration(32)) - self.assertEqual("2 days", geotrek_tags.duration(48)) - self.assertEqual("3 days", geotrek_tags.duration(49)) - self.assertEqual("8 days", geotrek_tags.duration(24 * 8)) - self.assertEqual("9 days", geotrek_tags.duration(24 * 9)) - - class TrekViewsSameStructureTests(AuthentFixturesTest): @classmethod def setUpTestData(cls): @@ -1451,7 +883,7 @@ def test_can_publish(self): self.assertContains(response, 'Published') -class ServiceViewsTest(GeotrekAPITestCase, CommonTest): +class ServiceViewsTest(CommonTest): model = Service modelfactory = ServiceFactory userfactory = TrekkingManagerFactory @@ -1460,17 +892,14 @@ class ServiceViewsTest(GeotrekAPITestCase, CommonTest): expected_column_list_extra = ['id', 'name', 'type', 'eid'] expected_column_formatlist_extra = ['id', 'type', 'eid'] - def get_expected_json_attrs(self): + def get_expected_geojson_geom(self): + return self.expected_json_geom + + def get_expected_geojson_attrs(self): return { - 'structure': { - 'id': self.obj.structure.pk, - 'name': 'My structure' - }, - 'type': { - 'id': self.obj.type.pk, - 'name': 'Service type', - 'pictogram': '/media/upload/service-type.png' - } + 'id': self.obj.pk, + 'name': str(self.obj), + 'published': self.obj.type.published } def get_expected_datatables_attrs(self): @@ -1546,34 +975,6 @@ def test_services_on_treks_not_public_anonymous(self): self.assertEqual(response.status_code, 404) -class ServiceJSONTest(TrekkingManagerTest): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.service = ServiceFactory.create(type__published=True) - cls.pk = cls.service.pk - - def setUp(self): - self.login() - - def test_list(self): - url = '/api/en/services.json' - self.response = self.client.get(url) - self.result = self.response.json() - self.assertEqual(len(self.result), 1) - self.assertTrue('type' in self.result[0]) - - def test_detail(self): - url = '/api/en/services/%s.json' % self.pk - self.response = self.client.get(url) - self.result = self.response.json() - self.assertDictEqual(self.result['type'], - {'id': self.service.type.pk, - 'name': self.service.type.name, - 'pictogram': os.path.join(settings.MEDIA_URL, self.service.type.pictogram.name), - }) - - class TrekPDFChangeAlongLinkedSignages(TestCase): def setUp(self): self.trek = TrekWithSignagesFactory.create() @@ -1652,78 +1053,3 @@ def test_delete_infrastructure_refreshes_pdf(self, mock_get): self.trek.infrastructures[0].delete() self.trek.refresh_from_db() self.assertFalse(is_file_uptodate(self.trek.get_map_image_path('fr'), self.trek.get_date_update())) - - -class TrekFilterTest(TestCase): - factory = TrekFactory - filterset = TrekFilterSet - - def test_provider_filter_without_provider(self): - filter_set = TrekFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - trek1 = TrekFactory.create(provider='my_provider1') - trek2 = TrekFactory.create(provider='my_provider2') - - filter_set = TrekFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(trek1, filter_set.qs) - self.assertIn(trek2, filter_set.qs) - - -class POIFilterTest(TestCase): - factory = POIFactory - filterset = POIFilterSet - - def test_provider_filter_without_provider(self): - filter_set = POIFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - poi1 = POIFactory.create(provider='my_provider1') - poi2 = POIFactory.create(provider='my_provider2') - - filter_set = POIFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(poi1, filter_set.qs) - self.assertIn(poi2, filter_set.qs) - - -class ServiceFilterTest(TestCase): - factory = ServiceFactory - filterset = ServiceFilterSet - - def test_provider_filter_without_provider(self): - filter_set = ServiceFilterSet(data={}) - filter_form = filter_set.form - - self.assertTrue(filter_form.is_valid()) - self.assertEqual(0, filter_set.qs.count()) - - def test_provider_filter_with_providers(self): - service1 = ServiceFactory.create(provider='my_provider1') - service2 = ServiceFactory.create(provider='my_provider2') - - filter_set = ServiceFilterSet() - filter_form = filter_set.form - - self.assertIn('', filter_form.as_p()) - self.assertIn('', filter_form.as_p()) - - self.assertIn(service1, filter_set.qs) - self.assertIn(service2, filter_set.qs) diff --git a/geotrek/trekking/urls.py b/geotrek/trekking/urls.py index bd74cfe20e..510d28f3eb 100644 --- a/geotrek/trekking/urls.py +++ b/geotrek/trekking/urls.py @@ -2,7 +2,6 @@ from django.urls import path, register_converter from mapentity.registry import registry -from rest_framework.routers import DefaultRouter from geotrek.altimetry.urls import AltimetryEntityOptions from geotrek.common.urls import PublishableEntityOptions, LangConverter @@ -12,7 +11,7 @@ from .views import ( TrekDocumentPublic, TrekDocumentBookletPublic, TrekMapImage, TrekMarkupPublic, TrekGPXDetail, TrekKMLDetail, WebLinkCreatePopup, TrekPOIViewSet, - TrekServiceViewSet, TrekAPIViewSet, POIAPIViewSet, ServiceAPIViewSet + TrekServiceViewSet ) register_converter(LangConverter, 'lang') @@ -23,7 +22,6 @@ path('api//treks//services.geojson', TrekServiceViewSet.as_view({'get': 'list'}), name="trek_service_geojson"), path('api//treks//.gpx', TrekGPXDetail.as_view(), name="trek_gpx_detail"), path('api//treks//.kml', TrekKMLDetail.as_view(), name="trek_kml_detail"), - path('api//treks//meta.html', TrekKMLDetail.as_view(), name="trek_meta"), path('popup/add/weblink/', WebLinkCreatePopup.as_view(), name='weblink_add'), path('image/trek--.png', TrekMapImage.as_view(), name='trek_map_image'), ] @@ -50,14 +48,6 @@ class ServiceEntityOptions(MapEntityOptions): pass -router = DefaultRouter(trailing_slash=False) - - -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/treks', TrekAPIViewSet, basename='trek') -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/pois', POIAPIViewSet, basename='poi') -router.register(r'^api/(?P[a-z]{2}(-[a-z]{2,4})?)/services', ServiceAPIViewSet, basename='service') - -urlpatterns += router.urls urlpatterns += registry.register(models.Trek, TrekEntityOptions, menu=settings.TREKKING_MODEL_ENABLED) urlpatterns += registry.register(models.POI, POIEntityOptions, menu=settings.POI_MODEL_ENABLED) urlpatterns += registry.register(models.Service, ServiceEntityOptions, menu=settings.SERVICE_MODEL_ENABLED) diff --git a/geotrek/trekking/views.py b/geotrek/trekking/views.py index 7929b8076f..9c4e431e82 100755 --- a/geotrek/trekking/views.py +++ b/geotrek/trekking/views.py @@ -1,14 +1,13 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.contrib.gis.db.models.functions import Transform -from django.db.models import Q from django.db.models.query import Prefetch from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404 from django.utils import translation from django.utils.decorators import method_decorator from django.utils.html import escape -from django.views.generic import CreateView, DetailView +from django.views.generic import CreateView from django.views.generic.detail import BaseDetailView from mapentity.helpers import alphabet_enumeration from mapentity.views import (MapEntityList, MapEntityFormat, MapEntityDetail, MapEntityMapImage, @@ -17,9 +16,8 @@ from geotrek.authent.decorators import same_structure_required from geotrek.common.forms import AttachmentAccessibilityForm -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.mixins.views import CompletenessMixin, CustomColumnsMixin from geotrek.common.models import Attachment, HDViewPoint, RecordSource, TargetPortal, Label from geotrek.common.permissions import PublicOrReadPermMixin from geotrek.common.views import DocumentPublic, DocumentBookletPublic, MarkupPublic @@ -34,10 +32,10 @@ from .filters import TrekFilterSet, POIFilterSet, ServiceFilterSet from .forms import TrekForm, TrekRelationshipFormSet, POIForm, WebLinkCreateFormPopup, ServiceForm -from .models import Trek, POI, WebLink, Service, TrekRelationship, OrderedTrekChild -from .serializers import (TrekGPXSerializer, TrekSerializer, POISerializer, ServiceSerializer, POIAPIGeojsonSerializer, - ServiceAPIGeojsonSerializer, TrekAPISerializer, TrekAPIGeojsonSerializer, POIAPISerializer, - ServiceAPISerializer, TrekGeojsonSerializer, POIGeojsonSerializer, ServiceGeojsonSerializer) +from .models import Trek, POI, WebLink, Service +from .serializers import (TrekGPXSerializer, TrekSerializer, POISerializer, ServiceSerializer, TrekPOIAPIGeojsonSerializer, + TrekServiceAPIGeojsonSerializer, + TrekGeojsonSerializer, POIGeojsonSerializer, ServiceGeojsonSerializer) class FlattenPicturesMixin: @@ -233,11 +231,6 @@ def dispatch(self, *args, **kwargs): return super().dispatch(*args, **kwargs) -class TrekMeta(MetaMixin, DetailView): - model = Trek - template_name = 'trekking/trek_meta.html' - - class TrekViewSet(GeotrekMapentityViewSet): model = Trek serializer_class = TrekSerializer @@ -255,35 +248,6 @@ def get_queryset(self): return qs -class TrekAPIViewSet(APIViewSet): - model = Trek - serializer_class = TrekAPISerializer - geojson_serializer_class = TrekAPIGeojsonSerializer - - def get_queryset(self): - qs = self.model.objects.existing() - qs = qs.select_related('structure', 'difficulty', 'practice', 'route', 'accessibility_level') - qs = qs.prefetch_related( - 'networks', 'source', 'portal', 'web_links', 'accessibilities', 'themes', 'aggregations', - 'information_desks', 'attachments', - Prefetch('trek_relationship_a', queryset=TrekRelationship.objects.select_related('trek_a', 'trek_b')), - Prefetch('trek_relationship_b', queryset=TrekRelationship.objects.select_related('trek_a', 'trek_b')), - Prefetch('trek_children', queryset=OrderedTrekChild.objects.select_related('parent', 'child')), - Prefetch('trek_parents', queryset=OrderedTrekChild.objects.select_related('parent', 'child')), - ) - qs = qs.filter(Q(published=True) | Q(trek_parents__parent__published=True)).distinct('practice__order', 'pk'). \ - order_by('-practice__order', 'pk') - if 'source' in self.request.GET: - qs = qs.filter(source__name__in=self.request.GET['source'].split(',')) - - if 'portal' in self.request.GET: - qs = qs.filter(Q(portal__name=self.request.GET['portal']) | Q(portal=None)) - - qs = qs.annotate(api_geom=Transform("geom", settings.API_SRID)) - - return qs - - class POIList(CustomColumnsMixin, FlattenPicturesMixin, MapEntityList): queryset = POI.objects.existing() filterform = POIFilterSet @@ -402,18 +366,9 @@ def get_queryset(self): return qs -class POIAPIViewSet(APIViewSet): - model = POI - serializer_class = POIAPISerializer - geojson_serializer_class = POIAPIGeojsonSerializer - - def get_queryset(self): - return POI.objects.existing().filter(published=True).annotate(api_geom=Transform("geom", settings.API_SRID)) - - class TrekPOIViewSet(viewsets.ModelViewSet): model = POI - serializer_class = POIAPIGeojsonSerializer + serializer_class = TrekPOIAPIGeojsonSerializer permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] def get_queryset(self): @@ -511,18 +466,9 @@ def get_queryset(self): return qs -class ServiceAPIViewSet(APIViewSet): - model = Service - serializer_class = ServiceAPISerializer - geojson_serializer_class = ServiceAPIGeojsonSerializer - - def get_queryset(self): - return Service.objects.existing().filter(type__published=True).annotate(api_geom=Transform("geom", settings.API_SRID)) - - class TrekServiceViewSet(viewsets.ModelViewSet): model = Service - serializer_class = ServiceAPIGeojsonSerializer + serializer_class = TrekServiceAPIGeojsonSerializer permission_classes = [rest_permissions.DjangoModelPermissionsOrAnonReadOnly] def get_queryset(self): diff --git a/geotrek/urls.py b/geotrek/urls.py index dad5f8135d..93a7212e70 100755 --- a/geotrek/urls.py +++ b/geotrek/urls.py @@ -55,8 +55,6 @@ urlpatterns.append(path('', include('geotrek.diving.urls'))) if 'geotrek.tourism' in settings.INSTALLED_APPS: urlpatterns.append(path('', include('geotrek.tourism.urls'))) -if 'geotrek.flatpages' in settings.INSTALLED_APPS: - urlpatterns.append(path('', include('geotrek.flatpages.urls'))) if 'geotrek.feedback' in settings.INSTALLED_APPS: urlpatterns.append(path('', include('geotrek.feedback.urls'))) if 'geotrek.sensitivity' in settings.INSTALLED_APPS: diff --git a/geotrek/zoning/locale/de/LC_MESSAGES/django.po b/geotrek/zoning/locale/de/LC_MESSAGES/django.po index f8086fd180..668799ec59 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 fb4aa90438..726f229665 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 fb4aa90438..726f229665 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 b9e9aadade..d41203d410 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 4fd1e00a43..a8683264fc 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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 fb4aa90438..726f229665 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: 2024-03-04 16:25+0100\n" +"POT-Creation-Date: 2024-03-22 14:32+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/serializers.py b/geotrek/zoning/serializers.py index 1b7b6f094e..ac76c589e5 100644 --- a/geotrek/zoning/serializers.py +++ b/geotrek/zoning/serializers.py @@ -48,12 +48,3 @@ class RestrictedAreaAPISerializer(serializers.ModelSerializer): class Meta: model = zoning_models.RestrictedArea fields = ('id', 'name', 'type') - - -class ZoningAPISerializerMixin(serializers.ModelSerializer): - cities = CityAPISerializer(many=True, source='published_cities') - districts = DistrictAPISerializer(many=True, source='published_districts') - areas = RestrictedAreaAPISerializer(many=True, source='published_areas') - - class Meta: - fields = ('cities', 'districts', 'areas') diff --git a/requirements.txt b/requirements.txt index 0ebc074ad0..03d999bcc8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -90,7 +90,7 @@ datetime==5.2 # via appy defusedxml==0.7.1 # via cairosvg -django==3.2.24 +django==3.2.25 # via # django-appconf # django-appypod @@ -218,7 +218,7 @@ large-image-source-vips==1.17.2 # via geotrek (setup.py) lxml==4.9.3 # via mapentity -mapentity==8.7.0 +mapentity==8.7.2 # via geotrek (setup.py) markdown==3.4.4 # via geotrek (setup.py)