{{ page.title }}
+{{ place.title }}
+ {% image place.image fill-560x420 %} +{{ place.description|richtext }}
+diff --git a/.babelrc b/.babelrc
new file mode 100644
index 00000000..bb571dd3
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,5 @@
+{
+ "presets":[
+ "es2015", "react"
+ ]
+}
diff --git a/.gitignore b/.gitignore
index f5ea6d46..0c32bcfd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,7 @@ db.sqlite3
/static
/components
node_modules/
+.idea/
+
+# Staticfiles bundled with Webpack
+naistenhelsinki/static/js/naistenhelsinki.js
diff --git a/content/templatetags/content_tags.py b/content/templatetags/content_tags.py
index 8a8f5f9c..ba1cf759 100644
--- a/content/templatetags/content_tags.py
+++ b/content/templatetags/content_tags.py
@@ -28,7 +28,7 @@ def has_menu_children(page):
# The has_menu_children method is necessary because the bootstrap menu requires
# a dropdown class to be applied to a parent
@register.inclusion_tag('tags/top_menu.html', takes_context=True)
-def top_menu(context, parent, calling_page=None):
+def top_menu(context, parent, calling_page=None, site_title='Digitaalinen Helsinki'):
menuitems = parent.get_children().live().in_menu()
for menuitem in menuitems:
menuitem.show_dropdown = has_menu_children(menuitem)
@@ -45,6 +45,7 @@ def top_menu(context, parent, calling_page=None):
return {
'calling_page': calling_page,
'menuitems': menuitems,
+ 'site_title': site_title,
# required by the pageurl tag that we want to use within this template
'request': context['request'],
}
diff --git a/digihel/settings.py b/digihel/settings.py
index e29733da..7f22aa87 100644
--- a/digihel/settings.py
+++ b/digihel/settings.py
@@ -40,6 +40,7 @@
'feedback',
'search',
'events',
+ 'naistenhelsinki',
'wagtail.wagtailforms',
'wagtail.wagtailredirects',
@@ -73,6 +74,7 @@
'django.contrib.auth',
'django.contrib.sites',
'django.contrib.contenttypes',
+ 'django.contrib.gis',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
@@ -121,7 +123,7 @@
DATABASES = {
'default': {
- 'ENGINE': 'django.db.backends.postgresql',
+ 'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'digihel',
'USER': os.environ.get('DATABASE_USER', 'digihel'),
}
diff --git a/digihel/templates/tags/top_menu.html b/digihel/templates/tags/top_menu.html
index d6ca6953..dcd4e65c 100644
--- a/digihel/templates/tags/top_menu.html
+++ b/digihel/templates/tags/top_menu.html
@@ -14,7 +14,7 @@
{# Link to home page #}
- Digitaalinen Helsinki
+ {{ site_title }}
diff --git a/digihel/urls.py b/digihel/urls.py
index b2a207c8..246a8c39 100644
--- a/digihel/urls.py
+++ b/digihel/urls.py
@@ -4,6 +4,7 @@
from wagtail.wagtailadmin import urls as wagtailadmin_urls
from wagtail.wagtailcore import urls as wagtail_urls
from wagtail.wagtaildocs import urls as wagtaildocs_urls
+from naistenhelsinki.views import places
from digi.views import sitemap_view
from events.views import event_data
@@ -26,6 +27,7 @@
url(r'^palaute/$', FeedbackView.as_view(), name='post_feedback'),
# client endpoints for external API data
+ url(r'^place_data/', places),
url(r'^event_data/', event_data),
url(r'', include(wagtail_urls)),
diff --git a/naistenhelsinki/__init__.py b/naistenhelsinki/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/naistenhelsinki/apps.py b/naistenhelsinki/apps.py
new file mode 100644
index 00000000..19b45999
--- /dev/null
+++ b/naistenhelsinki/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class NaistenhelsinkiConfig(AppConfig):
+ name = 'naistenhelsinki'
diff --git a/naistenhelsinki/migrations/0001_initial.py b/naistenhelsinki/migrations/0001_initial.py
new file mode 100644
index 00000000..b3545a92
--- /dev/null
+++ b/naistenhelsinki/migrations/0001_initial.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-09-26 08:11
+from __future__ import unicode_literals
+
+import django.contrib.gis.db.models.fields
+import django.contrib.gis.geos.point
+from django.db import migrations, models
+import django.db.models.deletion
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailcore.fields
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('wagtailimages', '0019_delete_filter'),
+ ('wagtailcore', '0039_collectionviewrestriction'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Place',
+ 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')),
+ ('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
+ ('description', wagtail.wagtailcore.fields.RichTextField(blank=True, verbose_name='kuvaus')),
+ ('location', django.contrib.gis.db.models.fields.PointField(blank=True, default=django.contrib.gis.geos.point.Point(24.945831, 60.192059), null=True, srid=4326, verbose_name='paikka')),
+ ('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+ ],
+ options={
+ 'ordering': ['sort_order'],
+ 'abstract': False,
+ },
+ bases=('wagtailcore.page', models.Model),
+ ),
+ migrations.CreateModel(
+ name='PlaceListPage',
+ 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=('wagtailcore.page',),
+ ),
+ migrations.CreateModel(
+ name='PlaceMapPage',
+ 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())))),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=('wagtailcore.page',),
+ ),
+ ]
diff --git a/naistenhelsinki/migrations/__init__.py b/naistenhelsinki/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/naistenhelsinki/models.py b/naistenhelsinki/models.py
new file mode 100644
index 00000000..0f1ff97a
--- /dev/null
+++ b/naistenhelsinki/models.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+from django.conf import settings
+from django.contrib.gis.forms.widgets import OSMWidget
+from django.contrib.gis.geos.point import Point
+from django.db import models
+from wagtail.wagtailcore import blocks
+from wagtail.wagtailcore.models import Page, Orderable
+from wagtail.wagtailcore.fields import RichTextField, StreamField
+from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel
+from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
+from wagtail.wagtailsearch import index
+from django.contrib.gis.db import models as geomodels
+
+HELSINKI = Point(24.945831, 60.192059)
+
+
+class PlaceMapPage(Page):
+ body = StreamField([
+ ('heading', blocks.CharBlock(classname="full title")),
+ ('paragraph', blocks.RichTextBlock()),
+ ])
+
+ content_panels = Page.content_panels + [
+ StreamFieldPanel('body')
+ ]
+
+ search_fields = Page.search_fields + [
+ index.SearchField('body')
+ ]
+
+
+class Place(Orderable, Page):
+ description = RichTextField("kuvaus", blank=True)
+ image = models.ForeignKey(
+ 'wagtailimages.Image',
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name='+',
+ )
+ location = geomodels.PointField(
+ "paikka",
+ null=True,
+ blank=True,
+ default=HELSINKI,
+ )
+
+ search_fields = Page.search_fields + [
+ index.SearchField('description'),
+ ]
+
+ content_panels = Page.content_panels + [
+ ImageChooserPanel('image'),
+ FieldPanel('description', classname="full"),
+ FieldPanel('location', classname="full", widget=OSMWidget())
+ ]
+
+ @property
+ def modal_title(self):
+ return self.title
+
+ @property
+ def image_url(self):
+ if not self.image:
+ return None
+ file_path = self.image.get_rendition('fill-900x500').file
+ return '{url_prefix}{file_path}'.format(
+ url_prefix=settings.MEDIA_URL,
+ file_path=file_path,
+ )
+
+
+class PlaceListPage(Page):
+
+ subpage_types = ['naistenhelsinki.Place']
+
+ def places(self):
+ return Place.objects.live()
diff --git a/naistenhelsinki/serializers.py b/naistenhelsinki/serializers.py
new file mode 100644
index 00000000..5b2723c1
--- /dev/null
+++ b/naistenhelsinki/serializers.py
@@ -0,0 +1,20 @@
+from django.contrib.gis.serializers.geojson import Serializer as GeoJsonSerializer
+
+
+class PlaceSerializer(GeoJsonSerializer):
+ """
+ A GeoJson serializer that can serialize property function values.
+ """
+
+ def serialize_property(self, obj):
+ model = type(obj)
+ for field in self.selected_fields:
+ if hasattr(model, field) and type(getattr(model, field)) == property:
+ self.handle_property(obj, field)
+
+ def handle_property(self, obj, field):
+ self._current[field] = getattr(obj, field)
+
+ def end_object(self, obj):
+ self.serialize_property(obj)
+ super(GeoJsonSerializer, self).end_object(obj)
diff --git a/naistenhelsinki/static/css/naistenhelsinki.scss b/naistenhelsinki/static/css/naistenhelsinki.scss
new file mode 100644
index 00000000..800e3231
--- /dev/null
+++ b/naistenhelsinki/static/css/naistenhelsinki.scss
@@ -0,0 +1,56 @@
+$modal-gray: #e5e5e5;
+
+.leaflet-container {
+ height: 600px;
+ width: 100%;
+
+ .number-icon {
+ text-align: center;
+ vertical-align: middle;
+ color: white;
+ line-height: 22px;
+ background: red;
+ -moz-border-radius: 50%;
+ -webkit-border-radius: 50%;
+ border-radius: 50%;
+ }
+}
+
+.ReactModal__Overlay {
+ z-index: 9999;
+}
+
+.ReactModal__Content {
+ @media (max-width: 500px) {
+ top: 15px !important;
+ left: 15px !important;
+ bottom: 15px !important;
+ right: 15px !important;
+ }
+}
+
+.nh-modal-header {
+ position: relative;
+ padding: 0;
+ margin-bottom: 15px;
+ border-bottom: 1px solid $modal-gray;
+
+ h1 {
+ margin-bottom: 15px;
+ }
+
+ .btn.close-modal {
+ float: right;
+ font-size: 30px;
+ line-height: 30px;
+ }
+}
+
+.nh-modal-image {
+ background: $modal-gray;
+ margin-bottom: 15px;
+
+ img {
+ margin: 0 auto;
+ }
+}
diff --git a/naistenhelsinki/static_src/js/naistenhelsinki/App.js b/naistenhelsinki/static_src/js/naistenhelsinki/App.js
new file mode 100644
index 00000000..ef45789c
--- /dev/null
+++ b/naistenhelsinki/static_src/js/naistenhelsinki/App.js
@@ -0,0 +1,37 @@
+import React, { Component } from 'react';
+
+import Map from './components/Map.jsx';
+
+
+const fetchPlaces = (f) => {
+ fetch('/place_data/').then((response) => {
+ // Convert to JSON
+ return response.json();
+ }).then((data) => {
+ f(data);
+ });
+};
+
+
+export default class App extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { places: false };
+ }
+
+ get_data() {
+ fetchPlaces((data) => this.setState({ places: data }));
+ }
+
+ componentDidMount() {
+ this.get_data();
+ }
+
+ render() {
+ return (
+
{{ place.description|richtext }}
+