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 0000000000000000000000000000000000000000..39b935e455ec104db15838dfd5767287664742e0 --- /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 91f65f364b723382dd4cd76ef6dcecedeb90e5fc..5c16fe01f1efbf819bdcb7cc7288982be9be145c 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 bb064432c763c887a6f7ffa4609787ef1de02c1d..823a17436634fa9e8d34cbf1026a6e2f26242a6e 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 0000000000000000000000000000000000000000..95344239bf4fa9ded866f3cba93e69d3c6ccdf33 --- /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 0000000000000000000000000000000000000000..a1e97f516f91d1ec14a9406f56c35279fe1452d2 --- /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 0000000000000000000000000000000000000000..db195aa39984c10e60a2e48af68cb0e883cd7692 --- /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 0000000000000000000000000000000000000000..a17bdac7f5c9ead7f8cd1d678c943e8f72906c04 --- /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