From 5c8340e220c24caae41055624511dd99ae840829 Mon Sep 17 00:00:00 2001
From: Todd Dembrey <todd.dembrey@torchbox.com>
Date: Thu, 18 Jan 2018 07:30:55 +0000
Subject: [PATCH] Allow storing of category options on the project

---
 .../migrations/0004_projectpage_categories.py | 21 +++++++++
 opentech/public/projects/models.py            | 12 ++++++
 .../templates/projects/project_page.html      | 13 ++++++
 .../projects/widgets/categories_widget.html   |  1 +
 .../projects/widgets/options_option.html      |  1 +
 .../projects/widgets/options_widget.html      | 15 +++++++
 opentech/public/projects/widgets.py           | 43 +++++++++++++++++++
 7 files changed, 106 insertions(+)
 create mode 100644 opentech/public/projects/migrations/0004_projectpage_categories.py
 create mode 100644 opentech/public/projects/templates/projects/widgets/categories_widget.html
 create mode 100644 opentech/public/projects/templates/projects/widgets/options_option.html
 create mode 100644 opentech/public/projects/templates/projects/widgets/options_widget.html
 create mode 100644 opentech/public/projects/widgets.py

diff --git a/opentech/public/projects/migrations/0004_projectpage_categories.py b/opentech/public/projects/migrations/0004_projectpage_categories.py
new file mode 100644
index 000000000..39b935e45
--- /dev/null
+++ b/opentech/public/projects/migrations/0004_projectpage_categories.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-01-17 22:22
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('projects', '0003_projectpage_status'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='projectpage',
+            name='categories',
+            field=models.TextField(default='{}'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/opentech/public/projects/models.py b/opentech/public/projects/models.py
index 91f65f364..5c16fe01f 100644
--- a/opentech/public/projects/models.py
+++ b/opentech/public/projects/models.py
@@ -1,3 +1,5 @@
+import json
+
 from django.db import models
 from django.conf import settings
 from django.core.exceptions import ValidationError
@@ -16,6 +18,7 @@ from wagtail.wagtailcore.fields import StreamField
 from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
 from wagtail.wagtailsearch import index
 
+from opentech.apply.categories.models import Option
 from opentech.public.utils.blocks import StoryBlock
 from opentech.public.utils.models import (
     BaseFunding,
@@ -24,6 +27,8 @@ from opentech.public.utils.models import (
     RelatedPage,
 )
 
+from .widgets import CategoriesWidget
+
 
 class ProjectContactDetails(models.Model):
     project_page = ParentalKey(
@@ -104,6 +109,8 @@ class ProjectPage(FundingMixin, BasePage):
     status = models.CharField(choices=STATUSES, max_length=25, default=STATUSES[0][0])
     body = StreamField(StoryBlock())
 
+    categories = models.TextField()
+
     search_fields = BasePage.search_fields + [
         index.SearchField('introduction'),
         index.SearchField('body'),
@@ -114,10 +121,15 @@ class ProjectPage(FundingMixin, BasePage):
         FieldPanel('introduction'),
         FieldPanel('status'),
         StreamFieldPanel('body'),
+        FieldPanel('categories', widget=CategoriesWidget),
         InlinePanel('contact_details', label="Contact Details"),
         InlinePanel('related_pages', label="Related Projects"),
     ] + FundingMixin.content_panels
 
+    def category_options(self):
+        categories = json.loads(self.categories)
+        options = [int(id) for options in categories.values() for id in options]
+        return Option.objects.select_related().filter(id__in=options).order_by('category')
 
 class ProjectIndexPage(BasePage):
     subpage_types = ['ProjectPage']
diff --git a/opentech/public/projects/templates/projects/project_page.html b/opentech/public/projects/templates/projects/project_page.html
index bb064432c..823a17436 100644
--- a/opentech/public/projects/templates/projects/project_page.html
+++ b/opentech/public/projects/templates/projects/project_page.html
@@ -31,4 +31,17 @@
     </div>
     {% include "utils/includes/funding.html" %}
 
+    {% regroup page.category_options by category as categories %}
+    {% for category, options in categories %}
+    <ul>
+        <li><h3>{{ category.name }}</h3>
+            <ul>
+                {% for option in options %}
+                <li>{{ option.value }}</li>
+                {% endfor %}
+            </ul>
+        </li>
+    </ul>
+    {% endfor %}
+
 {% endblock content %}
diff --git a/opentech/public/projects/templates/projects/widgets/categories_widget.html b/opentech/public/projects/templates/projects/widgets/categories_widget.html
new file mode 100644
index 000000000..95344239b
--- /dev/null
+++ b/opentech/public/projects/templates/projects/widgets/categories_widget.html
@@ -0,0 +1 @@
+{% spaceless %}<ul class="multiple">{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}</ul>{% endspaceless %}
diff --git a/opentech/public/projects/templates/projects/widgets/options_option.html b/opentech/public/projects/templates/projects/widgets/options_option.html
new file mode 100644
index 000000000..a1e97f516
--- /dev/null
+++ b/opentech/public/projects/templates/projects/widgets/options_option.html
@@ -0,0 +1 @@
+<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %} style="width:50%;">{{ widget.label }}</label><div class="field-content" style="width:50%;"><div class="input">{% include "django/forms/widgets/input.html" %}</div></div>
diff --git a/opentech/public/projects/templates/projects/widgets/options_widget.html b/opentech/public/projects/templates/projects/widgets/options_widget.html
new file mode 100644
index 000000000..db195aa39
--- /dev/null
+++ b/opentech/public/projects/templates/projects/widgets/options_widget.html
@@ -0,0 +1,15 @@
+<li>
+<h2>{{ widget.attrs.label_tag }}</h2>
+<fieldset>
+{% with id=widget.attrs.id %}
+<ul{% if id %} id="{{ id }}"{% endif %}class="fields {% if widget.attrs.class %}{{ widget.attrs.class }}{% endif %}">{% for group, options, index in widget.optgroups %}
+        {% for option in options %}
+        <li {% if id %} id="{{ id }}_{{ index }}"{% endif %}>
+            <div class="field checkbox_input boolean_field">
+            {% include option.template_name with widget=option %}{% endfor %}
+            </div>
+        </li>
+    {% endfor %}
+</ul>{% endwith %}
+</fieldset>
+</li>
diff --git a/opentech/public/projects/widgets.py b/opentech/public/projects/widgets.py
new file mode 100644
index 000000000..a17bdac7f
--- /dev/null
+++ b/opentech/public/projects/widgets.py
@@ -0,0 +1,43 @@
+import json
+
+from django import forms
+
+from opentech.apply.categories.models import Category
+
+
+class OptionsWidget(forms.CheckboxSelectMultiple):
+    template_name = 'projects/widgets/options_widget.html'
+    option_template_name = 'projects/widgets/options_option.html'
+
+class CategoriesWidget(forms.MultiWidget):
+    template_name = 'projects/widgets/categories_widget.html'
+
+    def __init__(self, *args, **kwargs):
+        widgets = [
+            OptionsWidget(
+                attrs={'id': cat.id, 'label_tag': cat.name},
+                choices=cat.options.all().values_list('id', 'value'),
+            )
+            for cat in Category.objects.all()
+        ]
+        kwargs['widgets'] = widgets
+        super().__init__(*args, **kwargs)
+
+    def decompress(self, value):
+        data = json.loads(value)
+        return [
+            data.get(str(widget.attrs['id']), list()) for widget in self.widgets
+        ]
+
+    def value_from_datadict(self, data, files, name):
+        data = {
+            widget.attrs['id']: widget.value_from_datadict(data, files, name + '_%s' % i)
+            for i, widget in enumerate(self.widgets)
+        }
+        return json.dumps(data)
+
+    def get_context(self, *args, **kwargs):
+        context = super().get_context(*args, **kwargs)
+        # Mutliwidget kills the wrap_label option when it is building the context
+        context['wrap_label'] = True
+        return context
-- 
GitLab