From 842d8cf9b11bc421602035405e0fc23b083c6524 Mon Sep 17 00:00:00 2001
From: Bryan Brancotte <bryan.brancotte@pasteur.fr>
Date: Tue, 15 Oct 2019 15:53:05 +0200
Subject: [PATCH] Adding ability to group preferences, and to add a description
 in each generated tab, only compatible with BS4

---
 .../templates/registration/account.html       | 67 ++++++++++++++-----
 .../templatetags/basetheme_bootstrap.py       |  6 ++
 basetheme_bootstrap/user_preferences.py       |  8 +++
 basetheme_bootstrap/views.py                  |  2 +
 setup.py                                      |  2 +-
 .../migrations/0002_auto_20191015_1154.py     | 33 +++++++++
 test_app_1/models.py                          | 30 +++++++++
 test_app_1/templates/test_app_1/base.html     |  1 +
 8 files changed, 131 insertions(+), 18 deletions(-)
 create mode 100644 test_app_1/migrations/0002_auto_20191015_1154.py
 create mode 100644 test_app_1/templates/test_app_1/base.html

diff --git a/basetheme_bootstrap/templates/registration/account.html b/basetheme_bootstrap/templates/registration/account.html
index 78b5b53..32a73d7 100644
--- a/basetheme_bootstrap/templates/registration/account.html
+++ b/basetheme_bootstrap/templates/registration/account.html
@@ -1,4 +1,5 @@
 {% extends basetheme_bootstrap_base_template %}
+{% load basetheme_bootstrap %}
 {% load crispy_forms_tags %}
 {% load static %}
 {% load sstatic %}
@@ -9,25 +10,57 @@
 
 
 {% block content %}
-<div class="col-12 col-xs-12 col-md-7 col-lg-8 col-xl-9 mb-sm-4 mb-md-0">
-    <div class="card panel panel-default">
-        <div class="card-header panel-heading">
-            <h3 class="panel-title">{%trans "Preferences"%}</h3>
-        </div>
-        <div class="card-body panel-body">
-            {%if form_prefs%}
-            <form method="post">
-                {% csrf_token %}
-                {{ form_prefs|crispy }}
-                <button class="btn btn-primary {{btn_classes}}" type="submit">Save my preferences
-                </button>
-            </form>
-            {%endif%}
+<div class="col-12 col-xs-12 col-md-7 col-lg-8 col-xl-9 mb-sm-4 mb-md-0 order-6">
+    <form method="post">{% csrf_token %}
+        <div class="card panel panel-default">
+            <nav class="card-header panel-heading">
+                <h3 class="panel-title">{%trans "Preferences"%}</h3>
+                {% if form_prefs.preferences_groups %}
+                <div class="nav nav-tabs card-header-tabs" id="nav-tab" role="tablist">
+                    {% for group_name, field_names in form_prefs.preferences_groups.items %}
+                    <a class="nav-item nav-link {% if forloop.first %}active{%endif%}"
+                       id="nav-{{ group_name|group_name_to_id }}"
+                       data-toggle="tab"
+                       href="#tab-{{ group_name|group_name_to_id }}"
+                       role="tab" aria-controls="nav-contact" aria-selected="false">
+                        {{group_name}}
+                    </a>
+                    {%endfor%}
+                </div>
+                {% endif %}
+            </nav>
+            <div class="card-body panel-body tab-content">
+                {%if form_prefs%}
+                {% if not form_prefs.preferences_groups %}
+                    {{ form_prefs|crispy }}
+                {%else%}
+                    {% for field in form_prefs.hidden_fields %}{{ field}}{% endfor %}
+                    {% for group_name, field_names in form_prefs.preferences_groups.items %}
+                        <div class="tab-pane fade {% if forloop.first %}active show{%endif%}" id="tab-{{ group_name|group_name_to_id }}" role="tabpanel" aria-labelledby="nav-{{ group_name|group_name_to_id }}">
+                            {{ group_name_desc }}
+                            {% for group_name_desc, description in form_prefs.preferences_groups_descriptions.items %}
+                            {% if group_name_desc == group_name %}
+                            <p>{{ description }}</p>
+                            {% endif%}
+                            {% endfor %}
+                            {% for field in form_prefs.visible_fields %}
+                            {% if field.name in field_names%}
+                                {{ field|as_crispy_field }}
+                            {% endif%}
+                            {% endfor %}
+                        </div>
+                    {% endfor %}
+                {%endif%}
+                {%endif%}
+            </div>
+            <div class="card-body panel-body pt-0">
+            <button class="btn btn-primary {{btn_classes}}" type="submit">Save my preferences
+            </button>
+            </div>
         </div>
-    </div>
-
+    </form>
 </div>
-<div class="col-12 col-xs-12 col-md-5 col-lg-4 col-xl-3">
+<div class="col-12 col-xs-12 col-md-5 col-lg-4 col-xl-3 order-6">
     <div class="card panel panel-default">
         <div class="card-header panel-heading">
             <h3 class="panel-title">{%trans "Actions"%}</h3>
diff --git a/basetheme_bootstrap/templatetags/basetheme_bootstrap.py b/basetheme_bootstrap/templatetags/basetheme_bootstrap.py
index 3230434..0576cb6 100644
--- a/basetheme_bootstrap/templatetags/basetheme_bootstrap.py
+++ b/basetheme_bootstrap/templatetags/basetheme_bootstrap.py
@@ -1,4 +1,5 @@
 import logging
+import re
 
 from django import template
 from django.conf import settings
@@ -44,6 +45,11 @@ def tags_to_bootstrap(tag):
     return tag
 
 
+@register.filter
+def group_name_to_id(group_name):
+    pattern = re.compile('[\W_]+')
+    return pattern.sub('', group_name)
+
 class IncludeIfExistsNode(template.Node):
     """
     A Node that instantiates an IncludeNode but wraps its render() in a
diff --git a/basetheme_bootstrap/user_preferences.py b/basetheme_bootstrap/user_preferences.py
index a3dc8f3..3d411dc 100644
--- a/basetheme_bootstrap/user_preferences.py
+++ b/basetheme_bootstrap/user_preferences.py
@@ -94,3 +94,11 @@ class UserPreferencesAbstractModel(models.Model):
             if field_name == "id" or field_name == "pk" or field_name == "user":
                 continue
             yield field_name
+
+    @property
+    def preferences_groups(self):
+        return None
+
+    @property
+    def preferences_groups_descriptions(self):
+        return {}
diff --git a/basetheme_bootstrap/views.py b/basetheme_bootstrap/views.py
index e9c6b81..a0fae35 100644
--- a/basetheme_bootstrap/views.py
+++ b/basetheme_bootstrap/views.py
@@ -159,6 +159,8 @@ def account_detail(request):
         for f in form_prefs.fields.values():
             if isinstance(f.widget, widgets.TimeInput):
                 f.widget.input_type = 'time'
+        form_prefs.preferences_groups = getattr(pref, "preferences_groups", None)
+        form_prefs.preferences_groups_descriptions = getattr(pref, "preferences_groups_descriptions", None)
     return render(request, 'registration/account.html', {
         'form_prefs': form_prefs,
         'btn_classes': 'pull-right float-right'
diff --git a/setup.py b/setup.py
index a5a27fd..c7dc6ac 100644
--- a/setup.py
+++ b/setup.py
@@ -7,7 +7,7 @@ readme = open('README.rst').read()
 
 setup(
     name='django-basetheme-bootstrap',
-    version='0.2.23',
+    version='0.2.24',
     description='Django Basetheme Bootstrap',
     long_description=readme,
     author='Bryan Brancotte',
diff --git a/test_app_1/migrations/0002_auto_20191015_1154.py b/test_app_1/migrations/0002_auto_20191015_1154.py
new file mode 100644
index 0000000..9c12418
--- /dev/null
+++ b/test_app_1/migrations/0002_auto_20191015_1154.py
@@ -0,0 +1,33 @@
+# Generated by Django 2.1.7 on 2019-10-15 11:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('test_app_1', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='myuserpreferences',
+            name='hide_help_2',
+            field=models.BooleanField(default=False, help_text='hide_help__help_text', verbose_name='hide_help_2__verbose_name'),
+        ),
+        migrations.AddField(
+            model_name='myuserpreferences',
+            name='hide_help_3',
+            field=models.BooleanField(default=False, help_text='hide_help__help_text', verbose_name='hide_help_3__verbose_name'),
+        ),
+        migrations.AddField(
+            model_name='myuserpreferences',
+            name='hide_help_4',
+            field=models.BooleanField(default=False, help_text='hide_help__help_text', verbose_name='hide_help_4__verbose_name'),
+        ),
+        migrations.AddField(
+            model_name='myuserpreferences',
+            name='hide_help_5',
+            field=models.BooleanField(default=False, help_text='hide_help__help_text', verbose_name='hide_help_5__verbose_name'),
+        ),
+    ]
diff --git a/test_app_1/models.py b/test_app_1/models.py
index 6cbd8fe..95537c7 100644
--- a/test_app_1/models.py
+++ b/test_app_1/models.py
@@ -6,11 +6,41 @@ from basetheme_bootstrap import user_preferences
 
 
 class MyUserPreferences(user_preferences.UserPreferencesAbstractModel, models.Model):
+    preferences_groups = {
+        "First group": {"hide_help", "hide_help_3", "hide_help_4", "a_time_field"},
+        "Second group": {"hide_help_2"},
+    }
+
+    preferences_groups_descriptions={
+        "First group":"this is the first group, check the other one",
+        "Second group":"this is the second group, check the other one",
+    }
+
     hide_help = models.BooleanField(
         verbose_name=_("hide_help__verbose_name"),
         help_text=_("hide_help__help_text"),
         default=False,
     )
+    hide_help_2 = models.BooleanField(
+        verbose_name=_("hide_help_2__verbose_name"),
+        help_text=_("hide_help__help_text"),
+        default=False,
+    )
+    hide_help_3 = models.BooleanField(
+        verbose_name=_("hide_help_3__verbose_name"),
+        help_text=_("hide_help__help_text"),
+        default=False,
+    )
+    hide_help_4 = models.BooleanField(
+        verbose_name=_("hide_help_4__verbose_name"),
+        help_text=_("hide_help__help_text"),
+        default=False,
+    )
+    hide_help_5 = models.BooleanField(
+        verbose_name=_("hide_help_5__verbose_name"),
+        help_text=_("hide_help__help_text"),
+        default=False,
+    )
     a_time_field = models.TimeField(
         verbose_name=_("a_time_field__verbose_name"),
         help_text=_("a_time_field_%(tz)s__help_text") % dict(tz=settings.TIME_ZONE),
diff --git a/test_app_1/templates/test_app_1/base.html b/test_app_1/templates/test_app_1/base.html
new file mode 100644
index 0000000..e3d651f
--- /dev/null
+++ b/test_app_1/templates/test_app_1/base.html
@@ -0,0 +1 @@
+{% extends "basetheme_bootstrap/base4.html" %}
\ No newline at end of file
-- 
GitLab