diff --git a/docs/changelog.rst b/docs/changelog.rst
index 87ed164a3b..1b4979962c 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,7 @@ CHANGELOG
2.102.1+dev (XXXX-XX-XX)
------------------------
+- Add popup button to add organizer in touristic event form
**New features**
diff --git a/geotrek/tourism/forms.py b/geotrek/tourism/forms.py
index a92670a311..cf5aef6169 100644
--- a/geotrek/tourism/forms.py
+++ b/geotrek/tourism/forms.py
@@ -4,9 +4,11 @@
from geotrek.tourism.widgets import AutoLocateMapWidget
from crispy_forms.layout import Div, HTML, Fieldset
+from mapentity.widgets import SelectMultipleWithPop
+
from .models import (TouristicContent, TouristicEvent, TouristicEventParticipantCount,
- TouristicEventParticipantCategory)
+ TouristicEventParticipantCategory, TouristicEventOrganizer)
from geotrek.common.forms import CommonForm
@@ -142,6 +144,11 @@ def __init__(self, *args, **kwargs):
self.fields['end_date'].widget.attrs['placeholder'] = _('dd/mm/yyyy')
self.fields['start_time'].widget.attrs['placeholder'] = _('HH:MM')
self.fields['end_time'].widget.attrs['placeholder'] = _('HH:MM')
+ if self.user.has_perm("tourism.add_touristiceventorganizer"):
+ self.fields['organizer'].widget = SelectMultipleWithPop(
+ choices=self.fields['organizer'].choices,
+ add_url=TouristicEventOrganizer.get_add_url()
+ )
# Since we use chosen() in trek_form.html, we don't need the default help text
for f in ['themes', 'source']:
self.fields[f].help_text = ''
@@ -189,3 +196,9 @@ def _save_m2m(self):
TouristicEventParticipantCount.objects.update_or_create(event=self.instance, category=category, defaults={'count': count})
else:
TouristicEventParticipantCount.objects.filter(event=self.instance, category=category).delete()
+
+
+class TouristicEventOrganizerFormPopup(CommonForm):
+ class Meta:
+ model = TouristicEventOrganizer
+ fields = ['label']
diff --git a/geotrek/tourism/models.py b/geotrek/tourism/models.py
index 5b685ba716..d8a40b9009 100644
--- a/geotrek/tourism/models.py
+++ b/geotrek/tourism/models.py
@@ -11,6 +11,7 @@
from django.dispatch import receiver
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _
+from django.urls import reverse
from easy_thumbnails.alias import aliases
from easy_thumbnails.exceptions import InvalidImageFormatError
from easy_thumbnails.files import get_thumbnailer
@@ -387,7 +388,7 @@ def __str__(self):
class TouristicEventOrganizer(TimeStampedModelMixin):
- label = models.CharField(verbose_name=_("Label"), max_length=256)
+ label = models.CharField(verbose_name=_("OrganizerLabel"), max_length=256)
class Meta:
verbose_name = _("Organizer")
@@ -397,6 +398,10 @@ class Meta:
def __str__(self):
return self.label
+ @classmethod
+ def get_add_url(cls):
+ return reverse('tourism:organizer_add')
+
class TouristicEvent(ZoningPropertiesMixin, AddPropertyMixin, PublishableMixin, GeotrekMapEntityMixin,
StructureRelated, PicturesMixin, TimeStampedModelMixin, NoDeleteMixin):
diff --git a/geotrek/tourism/templates/tourism/touristiceventorganizer_form.html b/geotrek/tourism/templates/tourism/touristiceventorganizer_form.html
new file mode 100644
index 0000000000..7ce60b145f
--- /dev/null
+++ b/geotrek/tourism/templates/tourism/touristiceventorganizer_form.html
@@ -0,0 +1,11 @@
+{% extends "mapentity/base_site.html" %}
+{% load static i18n crispy_forms_tags %}
+
+{% block title %}{% trans "Add Organizer" %}{% endblock title %}
+
+{% block navbar %}{% endblock navbar %}
+
+{% block content %}
+
{% trans "Add Organizer"%}
+{% crispy form %}
+{% endblock content %}
diff --git a/geotrek/tourism/tests/test_forms.py b/geotrek/tourism/tests/test_forms.py
index 16c3ed594a..be23bf4024 100644
--- a/geotrek/tourism/tests/test_forms.py
+++ b/geotrek/tourism/tests/test_forms.py
@@ -1,5 +1,10 @@
+
+from django.forms.widgets import Select
from django.test import TestCase
+from django.contrib.auth.models import Permission
+
+from mapentity.widgets import SelectMultipleWithPop
from geotrek.authent.tests.factories import UserFactory
from geotrek.tourism.forms import TouristicEventForm
@@ -85,3 +90,23 @@ def test_begin_end_date(self):
)
self.assertFalse(form1.is_valid())
self.assertIn("Start date is after end date", str(form1.errors))
+
+ def test_organizers_widget_select(self):
+ # if user has 'add_touristiceventorganizer' permission the widget must be a SelectMultipleWithPop
+ # otherwith a Select
+ form = TouristicEventForm(
+ user=UserFactory(),
+ data={}
+ )
+ assert type(form.fields['organizer'].widget) is Select
+
+ def test_organizers_widget_select_multiple_with_pop(self):
+ # if user has 'add_touristiceventorganizer' permission the widget must be a SelectMultipleWithPop
+ # otherwith a Select
+ user = UserFactory()
+ user.user_permissions.add(Permission.objects.get(codename='add_touristiceventorganizer'))
+ form = TouristicEventForm(
+ user=user,
+ data={}
+ )
+ assert type(form.fields['organizer'].widget) is SelectMultipleWithPop
diff --git a/geotrek/tourism/tests/test_models.py b/geotrek/tourism/tests/test_models.py
index 34ca55bfab..4cad65ba39 100644
--- a/geotrek/tourism/tests/test_models.py
+++ b/geotrek/tourism/tests/test_models.py
@@ -9,7 +9,7 @@
from mapentity.middleware import clear_internal_user_cache
from geotrek.core.tests import factories as core_factories
-from geotrek.tourism.models import TouristicContentType
+from geotrek.tourism.models import TouristicContentType, TouristicEventOrganizer
from geotrek.tourism.tests import factories as tourism_factories
from geotrek.tourism.tests.factories import (InformationDeskFactory,
InformationDeskTypeFactory,
@@ -149,6 +149,10 @@ def test_str(self):
organizer = tourism_factories.TouristicEventOrganizerFactory(label="foo bar")
self.assertEqual('foo bar', str(organizer))
+ def test_get_add_url(self):
+ url = TouristicEventOrganizer.get_add_url()
+ self.assertEqual(url, "/popup/add/organizer/")
+
class TouristicEventModelTest(TestCase):
def test_dates_display_no_end_date(self):
diff --git a/geotrek/tourism/tests/test_views.py b/geotrek/tourism/tests/test_views.py
index 86559b244e..5085aaed60 100644
--- a/geotrek/tourism/tests/test_views.py
+++ b/geotrek/tourism/tests/test_views.py
@@ -7,7 +7,8 @@
from unittest import mock
from django.conf import settings
-from django.contrib.auth.models import Group
+from django.contrib.auth.models import Group, Permission
+
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.test.utils import override_settings
@@ -486,6 +487,21 @@ def test_not_published_document_pdf(self):
self.assertEqual(response.status_code, 403)
+class TouristicEventOrganizerCreatePopupTest(TestCase):
+ def test_cannot_create_organizer(self):
+ url = '/popup/add/organizer/'
+ response = self.client.get(url)
+ self.assertRedirects(response, "/login/?next=/popup/add/organizer/")
+
+ def test_can_create_organizer(self):
+ user = UserFactory()
+ user.user_permissions.add(Permission.objects.get(codename='add_touristiceventorganizer'))
+ self.client.force_login(user=user)
+ url = '/popup/add/organizer/'
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+
+
class TouristicEventCustomViewTests(TrekkingManagerTest):
@mock.patch('mapentity.helpers.requests.get')
def test_public_document_pdf(self, mocked):
diff --git a/geotrek/tourism/urls.py b/geotrek/tourism/urls.py
index e0bc694d0d..6226e8eaf2 100644
--- a/geotrek/tourism/urls.py
+++ b/geotrek/tourism/urls.py
@@ -22,6 +22,8 @@
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'),
+
]
diff --git a/geotrek/tourism/views.py b/geotrek/tourism/views.py
index 9119e031df..f9fb011292 100644
--- a/geotrek/tourism/views.py
+++ b/geotrek/tourism/views.py
@@ -2,12 +2,16 @@
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
+from django.http import Http404, 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.views.generic import DetailView
+from django.utils.html import escape
+from django.views.generic import DetailView, CreateView
from django_filters.rest_framework import DjangoFilterBackend
from mapentity.views import (MapEntityCreate, MapEntityUpdate, MapEntityList, MapEntityDetail,
MapEntityDelete, MapEntityFormat, MapEntityDocument)
@@ -25,8 +29,8 @@
from geotrek.common.viewsets import GeotrekMapentityViewSet
from geotrek.trekking.models import Trek
from .filters import TouristicContentFilterSet, TouristicEventFilterSet, TouristicEventApiFilterSet
-from .forms import TouristicContentForm, TouristicEventForm
-from .models import (TouristicContent, TouristicEvent, TouristicContentCategory, InformationDesk)
+from .forms import TouristicContentForm, TouristicEventForm, TouristicEventOrganizerFormPopup
+from .models import (TouristicContent, TouristicEvent, TouristicContentCategory, TouristicEventOrganizer, InformationDesk)
from .serializers import (TouristicContentSerializer, TouristicEventSerializer,
TouristicContentAPIGeojsonSerializer, TouristicEventAPIGeojsonSerializer,
InformationDeskGeojsonSerializer, TouristicContentAPISerializer, TouristicEventAPISerializer,
@@ -275,6 +279,23 @@ def get_context_data(self, **kwargs):
return context
+class TouristicEventOrganizerCreatePopup(CreateView):
+ model = TouristicEventOrganizer
+ form_class = TouristicEventOrganizerFormPopup
+
+ @method_decorator(login_required)
+ def dispatch(self, request, *args, **kwargs):
+ if not request.user.has_perm("tourism.add_touristiceventorganizer"):
+ raise PermissionDenied
+ return super().dispatch(request, *args, **kwargs)
+
+ def form_valid(self, form):
+ self.object = form.save()
+ return HttpResponse("""
+
+ """ % (escape(form.instance._get_pk_val()), escape(form.instance)))
+
+
class TouristicEventDocumentPublic(TouristicEventDocumentPublicMixin, DocumentPublic):
pass