From 2dde8dad6c8027bb545d17cd2f2902c65d3af9e2 Mon Sep 17 00:00:00 2001
From: Valentin Deniaud <vdeniaud@entrouvert.com>
Date: Wed, 10 Nov 2021 15:21:57 +0100
Subject: [PATCH 1/2] public: add page selector in site settings (#58475)

---
 combo/data/migrations/0049_sitesettings.py    |  8 ++--
 .../migrations/0051_auto_20211110_1521.py     | 40 +++++++++++++++++++
 combo/data/models.py                          | 26 ++++++++++--
 .../widgets/select_with_other_option.html     |  8 ++++
 combo/manager/forms.py                        |  8 ++++
 .../templates/combo/site_settings.html        | 29 +++++++++++++-
 combo/public/views.py                         | 14 +++++--
 tests/test_manager.py                         | 12 ++++++
 tests/test_public.py                          | 23 +++++++++++
 9 files changed, 155 insertions(+), 13 deletions(-)
 create mode 100644 combo/data/migrations/0051_auto_20211110_1521.py
 create mode 100644 combo/data/templates/combo/widgets/select_with_other_option.html

diff --git a/combo/data/migrations/0049_sitesettings.py b/combo/data/migrations/0049_sitesettings.py
index 4a03999b..ff6b22f3 100644
--- a/combo/data/migrations/0049_sitesettings.py
+++ b/combo/data/migrations/0049_sitesettings.py
@@ -21,8 +21,8 @@ class Migration(migrations.Migration):
                     'initial_login_page_path',
                     models.CharField(
                         max_length=100,
-                        verbose_name='Initial login page path',
-                        help_text='Page to redirect to the first time user logs in.',
+                        verbose_name='',
+                        help_text='Path or full URL.',
                         blank=True,
                     ),
                 ),
@@ -30,8 +30,8 @@ class Migration(migrations.Migration):
                     'welcome_page_path',
                     models.CharField(
                         max_length=100,
-                        verbose_name='Welcome page path',
-                        help_text='Page to redirect to on the first visit, to suggest user to log in.',
+                        verbose_name='',
+                        help_text='Path or full URL.',
                         blank=True,
                     ),
                 ),
diff --git a/combo/data/migrations/0051_auto_20211110_1521.py b/combo/data/migrations/0051_auto_20211110_1521.py
new file mode 100644
index 00000000..3e29ee0b
--- /dev/null
+++ b/combo/data/migrations/0051_auto_20211110_1521.py
@@ -0,0 +1,40 @@
+# Generated by Django 2.2.19 on 2021-11-10 14:21
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('data', '0050_populate_site_settings'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='sitesettings',
+            name='initial_login_page',
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                related_name='+',
+                to='data.Page',
+                verbose_name='Initial login page',
+                help_text='Page to redirect to the first time user logs in.',
+            ),
+        ),
+        migrations.AddField(
+            model_name='sitesettings',
+            name='welcome_page',
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                related_name='+',
+                to='data.Page',
+                verbose_name='Welcome page',
+                help_text='Page to redirect to on the first visit, to suggest user to log in.',
+            ),
+        ),
+    ]
diff --git a/combo/data/models.py b/combo/data/models.py
index cd5362a6..6337b0d3 100644
--- a/combo/data/models.py
+++ b/combo/data/models.py
@@ -2209,15 +2209,33 @@ def cell_maintain_page_cell_cache(sender, instance=None, **kwargs):
 
 
 class SiteSettings(models.Model):
-    welcome_page_path = models.CharField(
-        _('Welcome page path'),
+    welcome_page = models.ForeignKey(
+        to=Page,
+        verbose_name=_('Welcome page'),
+        on_delete=models.SET_NULL,
+        null=True,
+        blank=True,
+        related_name='+',
         help_text=_('Page to redirect to on the first visit, to suggest user to log in.'),
+    )
+    welcome_page_path = models.CharField(
+        verbose_name='',
+        help_text=_('Path or full URL.'),
         max_length=100,
         blank=True,
     )
-    initial_login_page_path = models.CharField(
-        _('Initial login page path'),
+    initial_login_page = models.ForeignKey(
+        to=Page,
+        verbose_name=_('Initial login page'),
         help_text=_('Page to redirect to the first time user logs in.'),
+        on_delete=models.SET_NULL,
+        null=True,
+        blank=True,
+        related_name='+',
+    )
+    initial_login_page_path = models.CharField(
+        verbose_name='',
+        help_text=_('Path or full URL.'),
         max_length=100,
         blank=True,
     )
diff --git a/combo/data/templates/combo/widgets/select_with_other_option.html b/combo/data/templates/combo/widgets/select_with_other_option.html
new file mode 100644
index 00000000..9b0482b6
--- /dev/null
+++ b/combo/data/templates/combo/widgets/select_with_other_option.html
@@ -0,0 +1,8 @@
+{% load i18n %}
+
+<select name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>{% for group_name, group_choices, group_index in widget.optgroups %}{% if group_name %}
+  <optgroup label="{{ group_name }}">{% endif %}{% for option in group_choices %}
+  {% include option.template_name with widget=option %}{% endfor %}{% if group_name %}
+  </optgroup>{% endif %}{% endfor %}
+<!-- "other" option: -->  <option value="" data-other="true">{% trans "Other:" %}</option>
+</select>
diff --git a/combo/manager/forms.py b/combo/manager/forms.py
index e6b3eae0..baf4d78d 100644
--- a/combo/manager/forms.py
+++ b/combo/manager/forms.py
@@ -326,3 +326,11 @@ class SiteSettingsForm(forms.ModelForm):
     class Meta:
         model = SiteSettings
         fields = '__all__'
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['welcome_page'].widget.template_name = 'combo/widgets/select_with_other_option.html'
+        self.fields['welcome_page'].widget.attrs['class'] = 'page-selector'
+        self.fields['initial_login_page'].widget.template_name = 'combo/widgets/select_with_other_option.html'
+        self.fields['initial_login_page'].widget.attrs['class'] = 'page-selector'
+        self.fields['welcome_page'].queryset = self.fields['welcome_page'].queryset.filter(public=True)
diff --git a/combo/manager/templates/combo/site_settings.html b/combo/manager/templates/combo/site_settings.html
index 0e3bedb8..7a6b0846 100644
--- a/combo/manager/templates/combo/site_settings.html
+++ b/combo/manager/templates/combo/site_settings.html
@@ -7,12 +7,39 @@
 
 {% block content %}
 
-<form method="post">
+<form method="post" id="site-settings-form">
   {% csrf_token %}
   {{ form.as_p }}
   <div class="buttons">
     <button class="submit-button">{% trans "Save" %}</button>
     <a class="cancel" href="{% url 'combo-manager-homepage' %}">{% trans 'Cancel' %}</a>
   </div>
+
+  <script>
+  $('select.page-selector').each(function() {
+    field_id = $(this).attr('id')
+    custom_url_field_id = '#' + field_id + '_path'
+    if($(custom_url_field_id).val())
+      $(this).children('option[data-other=true]').prop('selected', true);
+  });
+
+  $('select').change(function(){
+    field_id = $(this).attr('id')
+    custom_url_field_id = '#' + field_id + '_path'
+    if ($('option:selected', this).data('other'))
+      $(custom_url_field_id).parent('p').show();
+    else
+      $(custom_url_field_id).parent('p').hide();
+  }).trigger('change');
+
+  $('form#site-settings-form').submit(function() {
+    $('select.page-selector').each(function() {
+      field_id = $(this).attr('id')
+      custom_url_field_id = '#' + field_id + '_path'
+      if(!$('option:selected', this).data('other'))
+        $(custom_url_field_id).val('');
+    });
+  });
+  </script>
 </form>
 {% endblock %}
diff --git a/combo/public/views.py b/combo/public/views.py
index 5b4a6a47..dd737cdf 100644
--- a/combo/public/views.py
+++ b/combo/public/views.py
@@ -463,7 +463,7 @@ def page(request):
     site_settings = SiteSettings.objects.get()
     if (
         parts == ['index']
-        and site_settings.initial_login_page_path
+        and (site_settings.initial_login_page or site_settings.initial_login_page_path)
         and (request.user and not request.user.is_anonymous)
     ):
         profile, dummy = Profile.objects.get_or_create(user=request.user)
@@ -471,18 +471,24 @@ def page(request):
             # first connection of user, record that and redirect to welcome URL
             profile.initial_login_view_timestamp = timezone.now()
             profile.save()
-            page_path = utils.get_templated_url(site_settings.initial_login_page_path)
+            if site_settings.initial_login_page:
+                page_path = site_settings.initial_login_page.get_online_url()
+            else:
+                page_path = utils.get_templated_url(site_settings.initial_login_page_path)
             return HttpResponseRedirect(page_path)
 
     if (
         parts == ['index']
-        and site_settings.welcome_page_path
+        and (site_settings.welcome_page or site_settings.welcome_page_path)
         and (not request.user or request.user.is_anonymous)
     ):
         if not request.session.setdefault('visited', False):
             # first visit, the user is not logged in.
             request.session['visited'] = True
-            page_path = utils.get_templated_url(site_settings.welcome_page_path)
+            if site_settings.welcome_page:
+                page_path = site_settings.welcome_page.get_online_url()
+            else:
+                page_path = utils.get_templated_url(site_settings.welcome_page_path)
             return HttpResponseRedirect(page_path)
 
     try:
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 5d05f35f..883e0825 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -2668,6 +2668,8 @@ def test_redirect_to_page_edit(app, admin_user):
 
 
 def test_site_settings(app, admin_user):
+    public_page = Page.objects.create(title='Public', slug='public')
+    private_page = Page.objects.create(title='Private', slug='private', public=False)
     app = login(app)
 
     resp = app.get('/manage/')
@@ -2687,3 +2689,13 @@ def test_site_settings(app, admin_user):
     site_settings.refresh_from_db()
     assert site_settings.welcome_page_path == ''
     assert site_settings.initial_login_page_path == ''
+
+    assert list(resp.context['form'].fields['welcome_page'].queryset) == [public_page]
+    assert list(resp.context['form'].fields['initial_login_page'].queryset) == [public_page, private_page]
+    resp.form['welcome_page'].select(text='Public')
+    resp.form['initial_login_page'].select(text='Private')
+    resp.form.submit()
+
+    site_settings.refresh_from_db()
+    assert site_settings.welcome_page == public_page
+    assert site_settings.initial_login_page == private_page
diff --git a/tests/test_public.py b/tests/test_public.py
index 29250e56..ed3a9f30 100644
--- a/tests/test_public.py
+++ b/tests/test_public.py
@@ -696,6 +696,20 @@ def test_initial_login_page(app, admin_user):
         resp = app.get('/', status=302)
         assert resp.location == 'https://authentic.example.org/bar/'
 
+    profile.initial_login_view_timestamp = None
+    profile.save(update_fields=['initial_login_view_timestamp'])
+
+    site_settings.initial_login_page = page
+    site_settings.save()
+
+    # first visit
+    app = login(app)
+    resp = app.get('/', status=302)
+    assert urllib.parse.urlparse(resp.location).path == '/initial-login/'
+
+    # visit again
+    resp = app.get('/', status=200)
+
 
 def test_welcome_page(app, admin_user):
     Page.objects.all().delete()
@@ -744,6 +758,15 @@ def test_welcome_page(app, admin_user):
         resp = app.get('/', status=302)
         assert resp.location == 'https://authentic.example.org/bar/'
 
+    site_settings.welcome_page = page
+    site_settings.save()
+
+    app.cookiejar.clear()
+    resp = app.get('/', status=302)
+    assert urllib.parse.urlparse(resp.location).path == '/welcome/'
+
+    resp = app.get('/', status=200)
+
 
 def test_post_cell(app):
     Page.objects.all().delete()
-- 
2.30.2

