diff --git a/content/templatetags/__init__.py b/content/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/content/templatetags/content_tags.py b/content/templatetags/content_tags.py index 55763ba6..8ecb8ae7 100644 --- a/content/templatetags/content_tags.py +++ b/content/templatetags/content_tags.py @@ -1,6 +1,14 @@ +import re +from collections import Sequence + +from bs4 import BeautifulSoup + from django import template from django.utils.safestring import mark_safe +from wagtail.wagtailcore.blocks.base import BoundBlock +from wagtail.wagtailcore.rich_text import RichText + register = template.Library() @@ -97,3 +105,86 @@ def sidebar_page_nav(page): html = list_children(parent, page) return mark_safe(html) + + +class TableOfContentsNode(template.Node): + def __init__(self, html_accessor, toc_var_name): + self.html_accessor = html_accessor + self.html_var = template.Variable(html_accessor) + self.toc_var_name = toc_var_name + + def render(self, context): + + blocks = self.html_var.resolve(context) + + if not isinstance(blocks, Sequence): + blocks = [blocks] + + headings_and_anchors = [] + toc_anchor_count = 1 + + for block in blocks: + if (not isinstance(block, BoundBlock) or not + isinstance(block.value, RichText)): + continue + soup = BeautifulSoup(block.value.source) + h2_tags = soup.find_all('h2') + + if not h2_tags: + continue + + for t in h2_tags: + # let's not add empty headings to table of contents + if not t.string.strip(): + continue + anchor = 'toc-{}'.format(toc_anchor_count) + headings_and_anchors.append((t.string, anchor)) + t.insert_before(soup.new_tag('span', id=anchor, **{'class':'toc-anchor'})) + toc_anchor_count += 1 + + new_source = str(soup) + # See wagtail.wagtailcore.rich_text expand_db_html and + # replace_embed_tag - if the embed tag doesn't close itself, + # replace_embed_tag doesn't recognize it, and can't replace it + # with img tag + new_source = new_source.replace('>', '/>') + block.value.source = new_source + + + context[self.toc_var_name] = headings_and_anchors + + return '' + + def __unicode__(self): + return u'String repr' + + +def do_table_of_contents(parser, token): + """ + Use like this: + {% do_table_of_contents as + %} + + This takes a WagTail BoundBlock or a list of them, and for each block with + RichText value, goes through H2 elements, and adds an anchor to them. It + puts out a list or headings and anchors, as in + [('Heading 1', 'toc-1'), ...], which can be used to render a table of + contents in the template. + """ + try: + # Splitting by None == splitting by spaces. + tag_name, arg = token.contents.split(None, 1) + except ValueError: + raise template.TemplateSyntaxError( + "%r tag requires arguments" % token.contents.split()[0] + ) + m = re.search(r'(.*?) as (\w+)', arg) + if not m: + raise template.TemplateSyntaxError("%r tag had invalid arguments" % + tag_name) + format_string, var_name = m.groups() + + return TableOfContentsNode(format_string, var_name) + + +register.tag('do_table_of_contents', do_table_of_contents) \ No newline at end of file diff --git a/digi/management/commands/update_indicators.py b/digi/management/commands/update_indicators.py new file mode 100644 index 00000000..f6bc5be4 --- /dev/null +++ b/digi/management/commands/update_indicators.py @@ -0,0 +1,45 @@ +from django.core.management.base import BaseCommand, CommandError +from digi.models import Indicator +from datetime import date, timedelta +import requests + +class Command(BaseCommand): + help = 'Fetches and saves new indicator values' + + def handle(self, *args, **options): + for indicator in Indicator.objects.all(): + + #POPULATION + if indicator.slug == 'vaesto': + continue + # indicator.value = indicator.value + 1 + + #HRI DATASETS + elif indicator.slug == 'tietoaineistot': + try: + resp = requests.get('http://hri.fi/api/3/action/package_list') + indicator.value = len(resp.json()['result']) + except Exception as e: + raise CommandError('Something went wrong while updating indicator {}. Retaining previous value {}.'.format(indicator.slug, indicator.value)) + + #OPEN311 ISSUES + elif indicator.slug == 'palaute': + try: + week_ago = (date.today() - timedelta(days=7)).isoformat() + resp = requests.get('https://asiointi.hel.fi/palautews/rest/v1/requests.json?status=closed&start_date={}'.format(week_ago)) + # today = date.today().isoformat() + # resp = requests.get('https://asiointi.hel.fi/palautews/rest/v1/requests.json?status=closed&start_date={}'.format(today)) + indicator.value = len(resp.json()) + except Exception as e: + raise CommandError('Something went wrong while updating indicator {}. Retaining previous value {}.'.format(indicator.slug, indicator.value)) + + elif indicator.slug == 'varaukset': + try: + resp = requests.get('http://api.hel.fi/respa/v1/reservation?all=true') + indicator.value = resp.json()['count'] + except Exception as e: + raise CommandError('Something went wrong while updating indicator {}. Retaining previous value {}.'.format(indicator.slug, indicator.value)) + + + indicator.save() + self.stdout.write(self.style.SUCCESS('Successfully updated indicator {}. Value is now {}'.format(indicator.slug, indicator.value))) diff --git a/digi/migrations/0009_guidefrontpage.py b/digi/migrations/0009_guidefrontpage.py new file mode 100644 index 00000000..681927ac --- /dev/null +++ b/digi/migrations/0009_guidefrontpage.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.10 on 2016-11-24 13:05 +from __future__ import unicode_literals + +import digihel.mixins +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0029_unicode_slugfield_dj19'), + ('digi', '0008_auto_20160909_1909'), + ] + + operations = [ + migrations.CreateModel( + name='GuideFrontPage', + 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')), + ], + options={ + 'abstract': False, + }, + bases=(digihel.mixins.RelativeURLMixin, 'wagtailcore.page'), + ), + ] diff --git a/digi/migrations/0010_guidecontentpage.py b/digi/migrations/0010_guidecontentpage.py new file mode 100644 index 00000000..f91c00c0 --- /dev/null +++ b/digi/migrations/0010_guidecontentpage.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2016-12-14 11:03 +from __future__ import unicode_literals + +import digihel.mixins +from django.db import migrations, models +import django.db.models.deletion +import wagtail.wagtailcore.blocks +import wagtail.wagtailcore.fields +import wagtail.wagtailimages.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0029_unicode_slugfield_dj19'), + ('digi', '0009_guidefrontpage'), + ] + + operations = [ + migrations.CreateModel( + name='GuideContentPage', + 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')), + ('body', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock())))), + ('sidebar', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock())))), + ], + options={ + 'abstract': False, + }, + bases=(digihel.mixins.RelativeURLMixin, 'wagtailcore.page'), + ), + ] diff --git a/digi/migrations/0011_fix_guidepages_fields.py b/digi/migrations/0011_fix_guidepages_fields.py new file mode 100644 index 00000000..21671f81 --- /dev/null +++ b/digi/migrations/0011_fix_guidepages_fields.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2016-12-21 13:35 +from __future__ import unicode_literals + +from django.db import migrations +import wagtail.wagtailcore.blocks +import wagtail.wagtailcore.fields +import wagtail.wagtailimages.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('digi', '0010_guidecontentpage'), + ] + + operations = [ + migrations.AlterField( + model_name='guidecontentpage', + name='body', + field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('raw_content', wagtail.wagtailcore.blocks.RawHTMLBlock()))), + ), + migrations.AlterField( + model_name='guidecontentpage', + name='sidebar', + field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock()), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock()), ('raw_content', wagtail.wagtailcore.blocks.RawHTMLBlock()))), + ), + ] diff --git a/digi/migrations/0012_themepage_twitter_hashtag.py b/digi/migrations/0012_themepage_twitter_hashtag.py new file mode 100644 index 00000000..0d7c2972 --- /dev/null +++ b/digi/migrations/0012_themepage_twitter_hashtag.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-01-16 08:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digi', '0011_fix_guidepages_fields'), + ] + + operations = [ + migrations.AddField( + model_name='themepage', + name='twitter_hashtag', + field=models.CharField(default='', max_length=255), + ), + ] diff --git a/digi/migrations/0013_add_KPI_fields.py b/digi/migrations/0013_add_KPI_fields.py new file mode 100644 index 00000000..03439bd1 --- /dev/null +++ b/digi/migrations/0013_add_KPI_fields.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-01-24 14:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digi', '0012_themepage_twitter_hashtag'), + ] + + operations = [ + migrations.AddField( + model_name='indicator', + name='illustration_filename', + field=models.CharField(default='images/hki-tietoaineisto.svg', max_length=100), + ), + migrations.AddField( + model_name='indicator', + name='slug', + field=models.CharField(default='', max_length=100), + ), + migrations.AddField( + model_name='indicator', + name='source_description', + field=models.CharField(default='', max_length=200), + ), + migrations.AddField( + model_name='indicator', + name='source_url', + field=models.CharField(default='http://dev.hel.fi/apis', max_length=100), + ), + ] diff --git a/digi/migrations/0014_projectpage_phase.py b/digi/migrations/0014_projectpage_phase.py new file mode 100644 index 00000000..006441d2 --- /dev/null +++ b/digi/migrations/0014_projectpage_phase.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-02-15 13:01 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('digi', '0013_add_KPI_fields'), + ] + + operations = [ + migrations.AddField( + model_name='projectpage', + name='phase', + field=models.CharField(choices=[('', 'No phase'), ('DI', 'Selvitys'), ('AL', 'Alfa'), ('BE', 'Beta'), ('LI', 'Tuotanto'), ('RE', 'Poisto')], default='', max_length=2), + ), + ] diff --git a/digi/models.py b/digi/models.py index 05542011..148b3f1e 100644 --- a/digi/models.py +++ b/digi/models.py @@ -1,24 +1,42 @@ -from blog.models import BlogCategory, BlogPage +from blog.models import BlogCategory, BlogPage, BlogIndexPage from django.db import models from django.utils.translation import ugettext_lazy as _ from modelcluster.fields import ParentalKey from modelcluster.models import ClusterableModel -from wagtail.wagtailadmin.edit_handlers import FieldPanel, InlinePanel, StreamFieldPanel +from wagtail.wagtailadmin.edit_handlers import ( + FieldPanel, InlinePanel, MultiFieldPanel, PageChooserPanel, StreamFieldPanel +) from wagtail.wagtailcore import blocks from wagtail.wagtailcore.fields import StreamField from wagtail.wagtailcore.models import Orderable, Page from wagtail.wagtailimages.edit_handlers import ImageChooserPanel +from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel +from wagtail.wagtailimages.blocks import ImageChooserBlock from wagtail.wagtailsearch import index from content.models import RelatedLink from digihel.mixins import RelativeURLMixin +from events.models import EventsIndexPage +rich_text_blocks = [ + ('heading', blocks.CharBlock(classname="full title")), + ('paragraph', blocks.RichTextBlock()), + ('image', ImageChooserBlock()), +] + +guide_blocks = rich_text_blocks + [ + ('raw_content', blocks.RawHTMLBlock()), +] class Indicator(models.Model): description = models.CharField(max_length=200) + slug = models.CharField(max_length=100, default='') value = models.IntegerField() # no history data for now order = models.IntegerField(null=True, blank=True) front_page = models.BooleanField(default=False) + illustration_filename = models.CharField(max_length=100, default='images/hki-tietoaineisto.svg') + source_description = models.CharField(max_length=200, default='') + source_url = models.CharField(max_length=100, default='http://dev.hel.fi/apis') sort_order_field = 'order' @@ -30,6 +48,22 @@ class Meta: def __str__(self): return self.description +class Phase(): + NONE = '' + DISCOVERY = 'DI' + ALPHA = 'AL' + BETA = 'BE' + LIVE = 'LI' + RETIREMENT = 'RE' + + PHASE_CHOICES = ( + (NONE, 'No phase'), + (DISCOVERY, 'Selvitys'), + (ALPHA, 'Alfa'), + (BETA, 'Beta'), + (LIVE, 'Tuotanto'), + (RETIREMENT, 'Poisto'), + ) class FooterLinkSection(ClusterableModel): title = models.CharField(max_length=100, null=True, blank=True) @@ -49,6 +83,8 @@ class FooterLink(Orderable, RelatedLink): section = ParentalKey('digi.FooterLinkSection', related_name='links') + + class ThemeIndexPage(RelativeURLMixin, Page): subpage_types = ['ThemePage'] @@ -57,6 +93,25 @@ def themes(self): return ThemePage.objects.all() +class GuideFrontPage(RelativeURLMixin, Page): + + @property + def blog_posts(self): + posts = BlogPage.objects.descendant_of(self).live().order_by('-date') + return posts + +class GuideContentPage(RelativeURLMixin, Page): + body = StreamField(guide_blocks) + sidebar = StreamField(guide_blocks) + + content_panels = Page.content_panels + [ + StreamFieldPanel('body'), + StreamFieldPanel('sidebar') + ] + search_fields = Page.search_fields + [ + index.SearchField('body') + ] + class ThemePage(RelativeURLMixin, Page): image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') @@ -67,6 +122,7 @@ class ThemePage(RelativeURLMixin, Page): ], null=True, blank=True) blog_category = models.ForeignKey(BlogCategory, help_text='Corresponding blog category', null=True, blank=True, on_delete=models.SET_NULL) + twitter_hashtag = models.CharField(max_length=255, default="") parent_page_types = ['ThemeIndexPage'] content_panels = Page.content_panels + [ @@ -74,6 +130,7 @@ class ThemePage(RelativeURLMixin, Page): FieldPanel('type'), FieldPanel('short_description'), FieldPanel('blog_category'), + FieldPanel('twitter_hashtag'), InlinePanel('roles', label=_("Roles")), InlinePanel('links', label=_("Links")), StreamFieldPanel('body'), @@ -106,12 +163,16 @@ def __str__(self): class ThemeLink(Orderable, RelatedLink): theme = ParentalKey('digi.ThemePage', related_name='links') - class ProjectPage(RelativeURLMixin, Page): type = _('Project') image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') short_description = models.TextField(null=True, blank=True) + phase = models.CharField( + max_length=2, + choices=Phase.PHASE_CHOICES, + default=Phase.NONE, + ) body = StreamField([ ('paragraph', blocks.RichTextBlock()), ], null=True, blank=True) @@ -119,6 +180,7 @@ class ProjectPage(RelativeURLMixin, Page): content_panels = Page.content_panels + [ ImageChooserPanel('image'), FieldPanel('short_description'), + FieldPanel('phase'), InlinePanel('roles', label=_("Roles")), InlinePanel('links', label=_("Links")), StreamFieldPanel('body'), @@ -162,9 +224,14 @@ def themes(self): @property def blog_posts(self): - posts = BlogPage.objects.all().order_by('-date') + main_blog_index = BlogIndexPage.objects.get(slug="blogikirjoitukset") + posts = BlogPage.objects.descendant_of(main_blog_index).live().order_by('-date') return posts + @property + def event_index(self): + return EventsIndexPage.objects.live().first() + @property def footer_link_sections(self): return FooterLinkSection.objects.order_by('sort_order') diff --git a/digi/templates/blog/blog_index_page.html b/digi/templates/blog/blog_index_page.html index 15657134..2dc83ba0 100644 --- a/digi/templates/blog/blog_index_page.html +++ b/digi/templates/blog/blog_index_page.html @@ -13,7 +13,7 @@

Blogikirjoitukset tagillä '{{ tag }}'

{% else %}
{% for blog in blogs|slice:":1" %}
diff --git a/digi/templates/blog/blog_post.html b/digi/templates/blog/blog_post.html index 94df7357..d0467411 100644 --- a/digi/templates/blog/blog_post.html +++ b/digi/templates/blog/blog_post.html @@ -1,4 +1,4 @@ -{% load wagtailcore_tags digi_tags static wagtailimages_tags %} +{% load wagtailcore_tags digi_tags static wagtailimages_tags social_widgets %} {% if include_context == "index_page" %}
@@ -7,7 +7,7 @@
{% endif %} @@ -32,20 +32,24 @@

{{ blog.title }}

-
+