diff --git a/opentech/public/funds/migrations/0002_labindex_labpage.py b/opentech/public/funds/migrations/0002_labindex_labpage.py new file mode 100644 index 0000000000000000000000000000000000000000..42af1ca714f3603957dcde5873c8592ad790e537 --- /dev/null +++ b/opentech/public/funds/migrations/0002_labindex_labpage.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-01-11 14:47 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import opentech.public.funds.blocks +import wagtail.wagtailcore.blocks +import wagtail.wagtailcore.fields +import wagtail.wagtaildocs.blocks +import wagtail.wagtailembeds.blocks +import wagtail.wagtailimages.blocks +import wagtail.wagtailsnippets.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('images', '0001_initial'), + ('wagtailcore', '0040_page_draft_title'), + ('public_funds', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='LabIndex', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('social_text', models.CharField(blank=True, max_length=255)), + ('listing_title', models.CharField(blank=True, help_text='Override the page title used when this page appears in listings', max_length=255)), + ('listing_summary', models.CharField(blank=True, help_text="The text summary used when this page appears in listings. It's also used as the description for search engines if the 'Search description' field above is not defined.", max_length=255)), + ('introduction', models.TextField(blank=True)), + ('header_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), + ('listing_image', models.ForeignKey(blank=True, help_text='Choose the image you wish to be displayed when this page appears in listings', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), + ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page', models.Model), + ), + migrations.CreateModel( + name='LabPage', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), + ('social_text', models.CharField(blank=True, max_length=255)), + ('listing_title', models.CharField(blank=True, help_text='Override the page title used when this page appears in listings', max_length=255)), + ('listing_summary', models.CharField(blank=True, help_text="The text summary used when this page appears in listings. It's also used as the description for search engines if the 'Search description' field above is not defined.", max_length=255)), + ('introduction', models.TextField(blank=True)), + ('lab_link', models.URLField(blank=True, verbose_name='External link')), + ('link_text', models.CharField(help_text='Text to display on the button', max_length=255)), + ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('quote', wagtail.wagtailcore.blocks.StructBlock((('quote', wagtail.wagtailcore.blocks.CharBlock(classname='title')), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False)), ('job_title', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('embed', wagtail.wagtailembeds.blocks.EmbedBlock()), ('call_to_action', wagtail.wagtailsnippets.blocks.SnippetChooserBlock('utils.CallToActionSnippet', template='blocks/call_to_action_block.html')), ('document', wagtail.wagtailcore.blocks.StructBlock((('document', wagtail.wagtaildocs.blocks.DocumentChooserBlock()), ('title', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('project_list', opentech.public.funds.blocks.ProjectsBlock()), ('reviewer_list', opentech.public.funds.blocks.ReviewersBlock())))), + ('header_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), + ('lab_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page')), + ('listing_image', models.ForeignKey(blank=True, help_text='Choose the image you wish to be displayed when this page appears in listings', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), + ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page', models.Model), + ), + ] diff --git a/opentech/public/funds/migrations/0003_icon_and_related_pages.py b/opentech/public/funds/migrations/0003_icon_and_related_pages.py new file mode 100644 index 0000000000000000000000000000000000000000..ba0f01802a568464799cae603f07efae67b5f8d8 --- /dev/null +++ b/opentech/public/funds/migrations/0003_icon_and_related_pages.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-01-12 15:19 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('images', '0001_initial'), + ('public_funds', '0002_labindex_labpage'), + ] + + operations = [ + migrations.AddField( + model_name='labpage', + name='icon', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage'), + ), + migrations.CreateModel( + name='LabPageRelatedPage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sort_order', models.IntegerField(blank=True, editable=False, null=True)), + ('page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.Page')), + ('source_page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_pages', to='public_funds.LabPage')), + ], + options={ + 'ordering': ['sort_order'], + 'abstract': False, + }, + ), + ] diff --git a/opentech/public/funds/models.py b/opentech/public/funds/models.py index c5b012db1a3d44ee8c7b50f9714d72da77918257..c027610f3196d27416c2158d6ce8407c4bc37fd1 100644 --- a/opentech/public/funds/models.py +++ b/opentech/public/funds/models.py @@ -1,15 +1,23 @@ from django.conf import settings +from django.core.exceptions import ValidationError from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db import models +from modelcluster.fields import ParentalKey from wagtail.wagtailadmin.edit_handlers import ( FieldPanel, + InlinePanel, + MultiFieldPanel, PageChooserPanel, StreamFieldPanel, ) from wagtail.wagtailcore.fields import StreamField +from wagtail.wagtailimages.edit_handlers import ImageChooserPanel -from opentech.public.utils.models import BasePage +from opentech.public.utils.models import ( + BasePage, + RelatedPage, +) from .blocks import FundBlock @@ -46,12 +54,98 @@ class FundIndex(BasePage): page = request.GET.get('page', 1) paginator = Paginator(funds, settings.DEFAULT_PER_PAGE) try: - news = paginator.page(page) + funds = paginator.page(page) + except PageNotAnInteger: + funds = paginator.page(1) + except EmptyPage: + funds = paginator.page(paginator.num_pages) + + context = super().get_context(request, *args, **kwargs) + context.update(subpages=funds) + return context + + +class LabPageRelatedPage(RelatedPage): + source_page = ParentalKey('LabPage', related_name='related_pages') + + +class LabPage(BasePage): + subpage_types = [] + parent_page_types = ['LabIndex'] + + introduction = models.TextField(blank=True) + icon = models.ForeignKey( + 'images.CustomImage', + null=True, + blank=True, + related_name='+', + on_delete=models.SET_NULL + ) + lab_type = models.ForeignKey( + 'wagtailcore.Page', + blank=True, + null=True, + on_delete=models.SET_NULL, + related_name='+', + ) + lab_link = models.URLField(blank=True, verbose_name='External link') + link_text = models.CharField(max_length=255, help_text='Text to display on the button') + body = StreamField(FundBlock()) + + content_panels = BasePage.content_panels + [ + ImageChooserPanel('icon'), + FieldPanel('introduction'), + MultiFieldPanel([ + # Limit to lab pages once created + PageChooserPanel('lab_type'), + FieldPanel('lab_link'), + FieldPanel('link_text'), + ], heading='Link for lab application'), + StreamFieldPanel('body'), + InlinePanel('related_pages', label="Related pages"), + ] + + @property + def link_to_lab(self): + return self.lab_link or self.lab_type.get_url() + + def clean(self): + if self.lab_type and self.lab_link: + raise ValidationError({ + 'lab_type': 'Cannot link to both a Lab page and external link', + 'lab_link': 'Cannot link to both a Lab page and external link', + }) + + if not self.lab_type and not self.lab_link: + raise ValidationError({ + 'lab_type': 'Please provide a way for applicants to apply', + 'lab_link': 'Please provide a way for applicants to apply', + }) + + +class LabIndex(BasePage): + subpage_types = ['LabPage'] + parent_page_types = ['home.HomePage'] + + introduction = models.TextField(blank=True) + + content_panels = BasePage.content_panels + [ + FieldPanel('introduction') + ] + + def get_context(self, request, *args, **kwargs): + labs = LabPage.objects.live().public().descendant_of(self) + + # Pagination + page = request.GET.get('page', 1) + paginator = Paginator(labs, settings.DEFAULT_PER_PAGE) + try: + labs = paginator.page(page) except PageNotAnInteger: - news = paginator.page(1) + labs = paginator.page(1) except EmptyPage: - news = paginator.page(paginator.num_pages) + labs = paginator.page(paginator.num_pages) context = super().get_context(request, *args, **kwargs) - context.update(news=news) + context.update(subpages=labs) return context diff --git a/opentech/public/funds/templates/public_funds/lab_index.html b/opentech/public/funds/templates/public_funds/lab_index.html new file mode 100644 index 0000000000000000000000000000000000000000..ce4f0b1db6ef2f27f9269c007d362c79d43b38d4 --- /dev/null +++ b/opentech/public/funds/templates/public_funds/lab_index.html @@ -0,0 +1 @@ +{% extends "standardpages/index_page.html" %} diff --git a/opentech/public/funds/templates/public_funds/lab_page.html b/opentech/public/funds/templates/public_funds/lab_page.html new file mode 100644 index 0000000000000000000000000000000000000000..2642ccfc89ba5f7287a0ffaaf9a2ff71d6ee6d13 --- /dev/null +++ b/opentech/public/funds/templates/public_funds/lab_page.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% load wagtailcore_tags wagtailimages_tags navigation_tags static %} + +{% block content %} + <div class="wrapper wrapper--flex"> + <section class="section section--main"> + <h1>{{ page.icon }}{{ page.title }}</h1> + <h5>{{ page.introduction }}</h5> + + {% include_block page.body %} + </section> + <section> + <a href="{{ page.link_to_lab }}">{{ page.link_text }}</a> + </section> + </div> + {% include "includes/relatedcontent.html" with related_pages=page.related_pages.all %} +{% endblock %}