Browse Source

Initial styling without merge errors

Edd Baldry 8 years ago
parent
commit
e0b93c6aec
47 changed files with 1466 additions and 368 deletions
  1. 0 1
      README
  2. 7 14
      bakerydemo/base/blocks.py
  3. 18 0
      bakerydemo/base/fixtures/bakerydemo.json
  4. 307 0
      bakerydemo/base/fixtures/breads.json
  5. 10 0
      bakerydemo/base/fixtures/collections.json
  6. 217 0
      bakerydemo/base/fixtures/images.json
  7. 0 0
      bakerydemo/base/management/__init__.py
  8. 0 0
      bakerydemo/base/management/commands/__init__.py
  9. 13 0
      bakerydemo/base/management/commands/load_initial_data.py
  10. 124 0
      bakerydemo/base/migrations/0001_initial.py
  11. 30 0
      bakerydemo/base/migrations/0002_auto_20170210_1217.py
  12. 23 0
      bakerydemo/base/migrations/0003_footertext.py
  13. 19 0
      bakerydemo/base/migrations/0004_auto_20170210_1420.py
  14. 67 37
      bakerydemo/base/models.py
  15. 13 2
      bakerydemo/base/templatetags/navigation_tags.py
  16. 82 0
      bakerydemo/blog/migrations/0001_initial.py
  17. 77 32
      bakerydemo/blog/models.py
  18. 77 0
      bakerydemo/breads/migrations/0001_initial.py
  19. 16 16
      bakerydemo/breads/models.py
  20. 62 0
      bakerydemo/locations/migrations/0001_initial.py
  21. 16 14
      bakerydemo/locations/models.py
  22. 0 0
      bakerydemo/search/__init__.py
  23. 57 0
      bakerydemo/search/views.py
  24. 5 2
      bakerydemo/settings/base.py
  25. 2 10
      bakerydemo/settings/dev.py
  26. 0 175
      bakerydemo/settings/production.py
  27. 4 4
      bakerydemo/templates/about_page.html
  28. 12 2
      bakerydemo/templates/base.html
  29. 1 1
      bakerydemo/templates/base/about_page.html
  30. 11 14
      bakerydemo/templates/base/form_page.html
  31. 7 9
      bakerydemo/templates/base/form_page_landing.html
  32. 4 0
      bakerydemo/templates/base/include/footer.html
  33. 8 8
      bakerydemo/templates/blocks/heading_block.html
  34. 2 4
      bakerydemo/templates/blocks/image_block.html
  35. 1 1
      bakerydemo/templates/blocks/paragraph_block.html
  36. 9 3
      bakerydemo/templates/blog/blog_index_page.html
  37. 4 4
      bakerydemo/templates/blog/blog_page.html
  38. 1 2
      bakerydemo/templates/breads/breads_index_page.html
  39. 38 2
      bakerydemo/templates/locations/location_page.html
  40. 14 0
      bakerydemo/templates/search/search_box.html
  41. 33 0
      bakerydemo/templates/search/search_results.html
  42. 0 4
      bakerydemo/templates/tags/sidebar_menu.html
  43. 1 2
      bakerydemo/templates/tags/top_menu.html
  44. 1 0
      bakerydemo/templates/tags/top_menu_children.html
  45. 4 2
      bakerydemo/urls.py
  46. 67 2
      readme.md
  47. 2 1
      vagrant/provision.sh

+ 0 - 1
README

@@ -1 +0,0 @@
-Hello

+ 7 - 14
bakerydemo/base/blocks.py

@@ -1,14 +1,7 @@
 from wagtail.wagtailimages.blocks import ImageChooserBlock
 from wagtail.wagtailimages.blocks import ImageChooserBlock
 from wagtail.wagtailembeds.blocks import EmbedBlock
 from wagtail.wagtailembeds.blocks import EmbedBlock
 from wagtail.wagtailcore.blocks import (
 from wagtail.wagtailcore.blocks import (
-    StructBlock,
-    TextBlock,
-    StreamBlock,
-    RichTextBlock,
-    CharBlock,
-    ListBlock,
-    ChoiceBlock,
-    PageChooserBlock
+    CharBlock, ChoiceBlock, RichTextBlock, StreamBlock, StructBlock, TextBlock,
 )
 )
 
 
 
 
@@ -25,11 +18,11 @@ class ImageBlock(StructBlock):
 class HeadingBlock(StructBlock):
 class HeadingBlock(StructBlock):
     heading_text = CharBlock(classname="title", required=True)
     heading_text = CharBlock(classname="title", required=True)
     size = ChoiceBlock(choices=[
     size = ChoiceBlock(choices=[
-            ('', 'Select a header size'),
-            ('h2', 'H2'),
-            ('h3', 'H3'),
-            ('h4', 'H4')
-        ], blank=True, required=False)
+        ('', 'Select a header size'),
+        ('h2', 'H2'),
+        ('h3', 'H3'),
+        ('h4', 'H4')
+    ], blank=True, required=False)
 
 
     class Meta:
     class Meta:
         icon = "title"
         icon = "title"
@@ -39,7 +32,7 @@ class HeadingBlock(StructBlock):
 class BlockQuote(StructBlock):
 class BlockQuote(StructBlock):
     text = TextBlock(),
     text = TextBlock(),
     attribute_name = CharBlock(
     attribute_name = CharBlock(
-            blank=True, required=False, label='e.g. Guy Picciotto')
+        blank=True, required=False, label='e.g. Guy Picciotto')
 
 
     class Meta:
     class Meta:
         icon = "fa-quote-left"
         icon = "fa-quote-left"

+ 18 - 0
bakerydemo/base/fixtures/bakerydemo.json

@@ -0,0 +1,18 @@
+[{
+    "model": "auth.user",
+    "pk": 1,
+    "fields": {
+        "password": "pbkdf2_sha256$30000$E9Uhcl4mkQtv$WxS3m0ZzdJXWmI7nAae9wmybd2L2HK5zgjHHXc65+oo=",
+        "last_login": null,
+        "is_superuser": true,
+        "username": "admin",
+        "first_name": "",
+        "last_name": "",
+        "email": "",
+        "is_staff": true,
+        "is_active": true,
+        "date_joined": "2017-02-10T12:48:21.920Z",
+        "groups": [],
+        "user_permissions": []
+    }
+}]

File diff suppressed because it is too large
+ 307 - 0
bakerydemo/base/fixtures/breads.json


+ 10 - 0
bakerydemo/base/fixtures/collections.json

@@ -0,0 +1,10 @@
+[{
+  "model": "wagtailcore.collection",
+  "pk": 2,
+  "fields": {
+    "path": "00010001",
+    "depth": 2,
+    "numchild": 0,
+    "name": "Breads"
+  }
+}]

+ 217 - 0
bakerydemo/base/fixtures/images.json

@@ -0,0 +1,217 @@
+[
+{
+  "model": "wagtailimages.image",
+  "pk": 2,
+  "fields": {
+    "collection": 2,
+    "title": "Anadama",
+    "file": "original_images/Anadama_bread_1.jpg",
+    "width": 4752,
+    "height": 3168,
+    "created_at": "2017-02-10T13:03:58.541Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 3,
+  "fields": {
+    "collection": 2,
+    "title": "Anpan",
+    "file": "original_images/Bean-jam-bunanpankatori-cityjapan.JPG",
+    "width": 800,
+    "height": 640,
+    "created_at": "2017-02-10T13:05:45.828Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 4,
+  "fields": {
+    "collection": 2,
+    "title": "Bazin",
+    "file": "original_images/Bazin.jpg",
+    "width": 1600,
+    "height": 1200,
+    "created_at": "2017-02-10T13:08:04.012Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 5,
+  "fields": {
+    "collection": 2,
+    "title": "Belgian Waffle",
+    "file": "original_images/Belgische_waffeln.jpg",
+    "width": 2025,
+    "height": 1536,
+    "created_at": "2017-02-10T13:10:05.988Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 6,
+  "fields": {
+    "collection": 2,
+    "title": "Bhakri",
+    "file": "original_images/Another_Vegetarian_Meal.jpg",
+    "width": 1600,
+    "height": 1200,
+    "created_at": "2017-02-10T13:15:25.730Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 7,
+  "fields": {
+    "collection": 2,
+    "title": "Black Bread",
+    "file": "original_images/Mischbrot-1.jpg",
+    "width": 2000,
+    "height": 1500,
+    "created_at": "2017-02-10T13:17:14.968Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 8,
+  "fields": {
+    "collection": 2,
+    "title": "Appam",
+    "file": "original_images/Appam_served_with_Coconut_Milk_in_Tamil_Nadu.JPG",
+    "width": 2362,
+    "height": 1064,
+    "created_at": "2017-02-10T13:19:24.588Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 9,
+  "fields": {
+    "collection": 1,
+    "title": "Arepa",
+    "file": "original_images/Arepa_asada.JPG",
+    "width": 1600,
+    "height": 1200,
+    "created_at": "2017-02-10T13:22:40.113Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 10,
+  "fields": {
+    "collection": 2,
+    "title": "Bolani",
+    "file": "original_images/Bolani_Afghan_bread_01.jpg",
+    "width": 1280,
+    "height": 960,
+    "created_at": "2017-02-10T13:24:28.559Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 11,
+  "fields": {
+    "collection": 2,
+    "title": "Baguette",
+    "file": "original_images/Baguette_de_pain_WikiCheese_Lausanne.jpg",
+    "width": 4032,
+    "height": 1958,
+    "created_at": "2017-02-10T13:24:47.838Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 12,
+  "fields": {
+    "collection": 2,
+    "title": "Bammie",
+    "file": "original_images/Bammies.jpg",
+    "width": 1700,
+    "height": 1527,
+    "created_at": "2017-02-10T13:27:19.252Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+},
+{
+  "model": "wagtailimages.image",
+  "pk": 13,
+  "fields": {
+    "collection": 1,
+    "title": "Bagel",
+    "file": "original_images/Plain-Bagel.jpg",
+    "width": 1920,
+    "height": 1660,
+    "created_at": "2017-02-10T13:28:30.500Z",
+    "uploaded_by_user": 1,
+    "focal_point_x": null,
+    "focal_point_y": null,
+    "focal_point_width": null,
+    "focal_point_height": null,
+    "file_size": null
+  }
+}]

+ 0 - 0
bakerydemo/base/management/__init__.py


+ 0 - 0
bakerydemo/base/management/commands/__init__.py


+ 13 - 0
bakerydemo/base/management/commands/load_initial_data.py

@@ -0,0 +1,13 @@
+import os
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+from django.core.management import call_command
+
+
+class Command(BaseCommand):
+    def handle(self, **options):
+        fixtures_dir = os.path.join(settings.BASE_DIR, 'base', 'fixtures')
+        fixture_file = os.path.join(fixtures_dir, 'bakerydemo.json')
+
+        call_command('loaddata', fixture_file, verbosity=0)

+ 124 - 0
bakerydemo/base/migrations/0001_initial.py

@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 12:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailcore.fields
+import wagtail.wagtailembeds.blocks
+import wagtail.wagtailimages.blocks
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('wagtailcore', '0032_add_bulk_delete_page_permission'),
+        ('wagtailimages', '0017_reduce_focal_point_key_max_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AboutLocationRelationship',
+            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)),
+            ],
+            options={
+                'abstract': False,
+                'ordering': ['sort_order'],
+            },
+        ),
+        migrations.CreateModel(
+            name='AboutPage',
+            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_block', wagtail.wagtailcore.blocks.StructBlock((('heading_text', wagtail.wagtailcore.blocks.CharBlock(classname='title', required=True)), ('size', wagtail.wagtailcore.blocks.ChoiceBlock(blank=True, choices=[('', 'Select a header size'), ('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4')], required=False))))), ('paragraph_block', wagtail.wagtailcore.blocks.RichTextBlock(icon='fa-paragraph', template='blocks/paragraph_block.html')), ('image_block', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False)), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('block_quote', wagtail.wagtailcore.blocks.StructBlock((('attribute_name', wagtail.wagtailcore.blocks.CharBlock(blank=True, label='e.g. Guy Picciotto', required=False)),))), ('embed_block', wagtail.wagtailembeds.blocks.EmbedBlock(help_text='Insert an embed URL e.g https://www.youtube.com/embed/SGJFWirQ3ks', icon='fa-s15', template='blocks/embed_block.html'))), blank=True, verbose_name='About page detail')),
+                ('image', models.ForeignKey(blank=True, help_text='Location image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='FormField',
+            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)),
+                ('label', models.CharField(help_text='The label of the form field', max_length=255, verbose_name='label')),
+                ('field_type', models.CharField(choices=[('singleline', 'Single line text'), ('multiline', 'Multi-line text'), ('email', 'Email'), ('number', 'Number'), ('url', 'URL'), ('checkbox', 'Checkbox'), ('checkboxes', 'Checkboxes'), ('dropdown', 'Drop down'), ('radio', 'Radio buttons'), ('date', 'Date'), ('datetime', 'Date/time')], max_length=16, verbose_name='field type')),
+                ('required', models.BooleanField(default=True, verbose_name='required')),
+                ('choices', models.TextField(blank=True, help_text='Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.', verbose_name='choices')),
+                ('default_value', models.CharField(blank=True, help_text='Default value. Comma separated values supported for checkboxes.', max_length=255, verbose_name='default value')),
+                ('help_text', models.CharField(blank=True, max_length=255, verbose_name='help text')),
+            ],
+            options={
+                'abstract': False,
+                'ordering': ['sort_order'],
+            },
+        ),
+        migrations.CreateModel(
+            name='FormPage',
+            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')),
+                ('to_address', models.CharField(blank=True, help_text='Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.', max_length=255, verbose_name='to address')),
+                ('from_address', models.CharField(blank=True, max_length=255, verbose_name='from address')),
+                ('subject', models.CharField(blank=True, max_length=255, verbose_name='subject')),
+                ('body', wagtail.wagtailcore.fields.StreamField((('heading_block', wagtail.wagtailcore.blocks.StructBlock((('heading_text', wagtail.wagtailcore.blocks.CharBlock(classname='title', required=True)), ('size', wagtail.wagtailcore.blocks.ChoiceBlock(blank=True, choices=[('', 'Select a header size'), ('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4')], required=False))))), ('paragraph_block', wagtail.wagtailcore.blocks.RichTextBlock(icon='fa-paragraph', template='blocks/paragraph_block.html')), ('image_block', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False)), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('block_quote', wagtail.wagtailcore.blocks.StructBlock((('attribute_name', wagtail.wagtailcore.blocks.CharBlock(blank=True, label='e.g. Guy Picciotto', required=False)),))), ('embed_block', wagtail.wagtailembeds.blocks.EmbedBlock(help_text='Insert an embed URL e.g https://www.youtube.com/embed/SGJFWirQ3ks', icon='fa-s15', template='blocks/embed_block.html'))))),
+                ('thank_you_text', wagtail.wagtailcore.fields.RichTextField(blank=True)),
+                ('header_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='GalleryPage',
+            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')),
+                ('introduction', models.TextField(blank=True, help_text='Text to describe the index page')),
+                ('choices', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wagtailcore.Collection')),
+                ('image', models.ForeignKey(blank=True, help_text='Location listing image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='HomePage',
+            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_block', wagtail.wagtailcore.blocks.StructBlock((('heading_text', wagtail.wagtailcore.blocks.CharBlock(classname='title', required=True)), ('size', wagtail.wagtailcore.blocks.ChoiceBlock(blank=True, choices=[('', 'Select a header size'), ('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4')], required=False))))), ('paragraph_block', wagtail.wagtailcore.blocks.RichTextBlock(icon='fa-paragraph', template='blocks/paragraph_block.html')), ('image_block', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False)), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('block_quote', wagtail.wagtailcore.blocks.StructBlock((('attribute_name', wagtail.wagtailcore.blocks.CharBlock(blank=True, label='e.g. Guy Picciotto', required=False)),))), ('embed_block', wagtail.wagtailembeds.blocks.EmbedBlock(help_text='Insert an embed URL e.g https://www.youtube.com/embed/SGJFWirQ3ks', icon='fa-s15', template='blocks/embed_block.html'))), blank=True, verbose_name='Home page detail')),
+                ('image', models.ForeignKey(blank=True, help_text='Location image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='People',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('first_name', models.CharField(max_length=254, verbose_name='First name')),
+                ('last_name', models.CharField(max_length=254, verbose_name='Last name')),
+                ('job_title', models.CharField(max_length=254, verbose_name='Job title')),
+                ('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'verbose_name_plural': 'People',
+                'verbose_name': 'Person',
+            },
+        ),
+        migrations.AddField(
+            model_name='formfield',
+            name='page',
+            field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='form_fields', to='base.FormPage'),
+        ),
+    ]

+ 30 - 0
bakerydemo/base/migrations/0002_auto_20170210_1217.py

@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 12:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('locations', '0001_initial'),
+        ('base', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='aboutlocationrelationship',
+            name='locations',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='about_location_relationship', to='locations.LocationPage'),
+        ),
+        migrations.AddField(
+            model_name='aboutlocationrelationship',
+            name='page',
+            field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='location_about_relationship', to='base.AboutPage'),
+        ),
+    ]

+ 23 - 0
bakerydemo/base/migrations/0003_footertext.py

@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 13:38
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import wagtail.wagtailcore.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('base', '0002_auto_20170210_1217'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='FooterText',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('body', wagtail.wagtailcore.fields.RichTextField()),
+            ],
+        ),
+    ]

+ 19 - 0
bakerydemo/base/migrations/0004_auto_20170210_1420.py

@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 14:20
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('base', '0003_footertext'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='footertext',
+            options={'verbose_name_plural': 'Footer Text'},
+        ),
+    ]

+ 67 - 37
bakerydemo/base/models.py

@@ -1,26 +1,24 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 from django.db import models
 from django.db import models
-from django.db.utils import OperationalError
 
 
 from modelcluster.fields import ParentalKey
 from modelcluster.fields import ParentalKey
 from modelcluster.models import ClusterableModel
 from modelcluster.models import ClusterableModel
-from wagtail.wagtailcore.models import Page, Orderable, Collection
-from wagtail.wagtailsearch import index
-from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
-from wagtail.wagtailcore.fields import StreamField, RichTextField
+
+from wagtail.contrib.modeladmin.options import (
+    ModelAdmin, ModelAdminGroup, modeladmin_register)
 from wagtail.wagtailadmin.edit_handlers import (
 from wagtail.wagtailadmin.edit_handlers import (
-        FieldPanel,
-        InlinePanel,
-        FieldRowPanel,
-        StreamFieldPanel,
-        MultiFieldPanel,
-        PageChooserPanel
-        )
+    FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel,
+    PageChooserPanel, StreamFieldPanel,
+)
+from wagtail.wagtailcore.fields import RichTextField, StreamField
+from wagtail.wagtailcore.models import Collection, Orderable, Page
+from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
+from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
+from wagtail.wagtailsearch import index
 from wagtail.wagtailsnippets.models import register_snippet
 from wagtail.wagtailsnippets.models import register_snippet
-from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
+
 from .blocks import BaseStreamBlock
 from .blocks import BaseStreamBlock
-from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
 
 
 
 
 @register_snippet
 @register_snippet
@@ -44,14 +42,43 @@ class People(ClusterableModel):
         ImageChooserPanel('image')
         ImageChooserPanel('image')
     ]
     ]
 
 
+    search_fields = Page.search_fields + [
+        index.SearchField('first_name'),
+        index.SearchField('last_name'),
+    ]
+
+    @property
+    def thumb_image(self):
+        # fail silently if there is no profile pic or the rendition file can't
+        # be found. Note @richbrennan worked out how to do this...
+        try:
+            return self.image.get_rendition('fill-50x50').img_tag()
+        except:
+            return ''
+
     def __str__(self):
     def __str__(self):
-        return self.first_name + " " + self.last_name
+        return '{} {}'.format(self.first_name, self.last_name)
 
 
     class Meta:
     class Meta:
         verbose_name = 'Person'
         verbose_name = 'Person'
         verbose_name_plural = 'People'
         verbose_name_plural = 'People'
 
 
 
 
+@register_snippet
+class FooterText(models.Model):
+    body = RichTextField()
+
+    panels = [
+        FieldPanel('body'),
+    ]
+
+    def __str__(self):
+        return "Footer text"
+
+    class Meta:
+        verbose_name_plural = 'Footer Text'
+
+
 class AboutLocationRelationship(Orderable, models.Model):
 class AboutLocationRelationship(Orderable, models.Model):
     """
     """
     This defines the relationship between the `LocationPage` within the `locations`
     This defines the relationship between the `LocationPage` within the `locations`
@@ -84,7 +111,7 @@ class AboutPage(Page):
 
 
     body = StreamField(
     body = StreamField(
         BaseStreamBlock(), verbose_name="About page detail", blank=True
         BaseStreamBlock(), verbose_name="About page detail", blank=True
-        )
+    )
     # We've defined the StreamBlock() within blocks.py that we've imported on
     # We've defined the StreamBlock() within blocks.py that we've imported on
     # line 12. Defining it in a different file gives us consistency across the
     # line 12. Defining it in a different file gives us consistency across the
     # site, though StreamFields _can_ be created on a per model basis if you
     # site, though StreamFields _can_ be created on a per model basis if you
@@ -94,10 +121,10 @@ class AboutPage(Page):
         ImageChooserPanel('image'),
         ImageChooserPanel('image'),
         StreamFieldPanel('body'),
         StreamFieldPanel('body'),
         InlinePanel(
         InlinePanel(
-           'location_about_relationship',
-           label='Locations',
-           min_num=None
-           ),
+            'location_about_relationship',
+            label='Locations',
+            min_num=None
+        ),
     ]
     ]
 
 
     # parent_page_types = [
     # parent_page_types = [
@@ -112,22 +139,6 @@ class AboutPage(Page):
     # api_fields = ['image', 'body']
     # api_fields = ['image', 'body']
 
 
 
 
-def getImageCollections():
-    # We return all collections to a list that don't have the name root.
-    try:
-        collection_images = [(
-            collection.id, collection.name
-            ) for collection in Collection.objects.all().exclude(
-            name='Root'
-            )]
-        return collection_images
-    except:
-        return [('', '')]
-
-    def __str__(self):
-        return self.title
-
-
 class HomePage(Page):
 class HomePage(Page):
     """
     """
     The Home Page
     The Home Page
@@ -143,7 +154,7 @@ class HomePage(Page):
 
 
     body = StreamField(
     body = StreamField(
         BaseStreamBlock(), verbose_name="Home page detail", blank=True
         BaseStreamBlock(), verbose_name="Home page detail", blank=True
-        )
+    )
 
 
     content_panels = Page.content_panels + [
     content_panels = Page.content_panels + [
         ImageChooserPanel('image'),
         ImageChooserPanel('image'),
@@ -223,3 +234,22 @@ class FormPage(AbstractEmailForm):
             FieldPanel('subject'),
             FieldPanel('subject'),
         ], "Email"),
         ], "Email"),
     ]
     ]
+
+
+class PeopleModelAdmin(ModelAdmin):
+    model = People
+    menu_label = 'People'  # ditch this to use verbose_name_plural from model
+    menu_icon = 'fa-people'  # change as required
+    list_display = ('first_name', 'last_name', 'job_title', 'thumb_image')
+
+
+class MyModelAdminGroup(ModelAdminGroup):
+    menu_label = 'WagtailBakery'
+    menu_icon = 'folder-open-inverse'  # change as required
+    menu_order = 200  # will put in 3rd place (000 being 1st, 100 2nd)
+    items = (PeopleModelAdmin,)
+
+
+# When using a ModelAdminGroup class to group several ModelAdmin classes together,
+# you only need to register the ModelAdminGroup class with Wagtail:
+modeladmin_register(MyModelAdminGroup)

+ 13 - 2
bakerydemo/base/templatetags/navigation_tags.py

@@ -1,8 +1,10 @@
 from django import template
 from django import template
-from django.template import Template
-from django.utils.http import urlencode
+
 from wagtail.wagtailcore.models import Page
 from wagtail.wagtailcore.models import Page
 
 
+from bakerydemo.base.models import FooterText
+
+
 register = template.Library()
 register = template.Library()
 # https://docs.djangoproject.com/en/1.9/howto/custom-template-tags/
 # https://docs.djangoproject.com/en/1.9/howto/custom-template-tags/
 
 
@@ -87,3 +89,12 @@ def breadcrumbs(context):
         'ancestors': ancestors,
         'ancestors': ancestors,
         'request': context['request'],
         'request': context['request'],
     }
     }
+
+
+@register.inclusion_tag('base/include/footer.html', takes_context=True)
+def get_footer_text(context):
+    footer_text = FooterText.objects.first().body
+
+    return {
+        'footer_text': footer_text,
+    }

+ 82 - 0
bakerydemo/blog/migrations/0001_initial.py

@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 12:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.contrib.taggit
+import modelcluster.fields
+import wagtail.contrib.wagtailroutablepage.models
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailcore.fields
+import wagtail.wagtailembeds.blocks
+import wagtail.wagtailimages.blocks
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('wagtailcore', '0032_add_bulk_delete_page_permission'),
+        ('wagtailimages', '0017_reduce_focal_point_key_max_length'),
+        ('base', '0001_initial'),
+        ('taggit', '0002_auto_20150616_2121'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='BlogIndexPage',
+            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')),
+                ('introduction', models.TextField(blank=True, help_text='Text to describe the index page')),
+                ('image', models.ForeignKey(blank=True, help_text='Location listing image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=(wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin, 'wagtailcore.page'),
+        ),
+        migrations.CreateModel(
+            name='BlogPage',
+            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')),
+                ('date_published', models.DateField(blank=True, null=True, verbose_name='Date article published')),
+                ('body', wagtail.wagtailcore.fields.StreamField((('heading_block', wagtail.wagtailcore.blocks.StructBlock((('heading_text', wagtail.wagtailcore.blocks.CharBlock(classname='title', required=True)), ('size', wagtail.wagtailcore.blocks.ChoiceBlock(blank=True, choices=[('', 'Select a header size'), ('h2', 'H2'), ('h3', 'H3'), ('h4', 'H4')], required=False))))), ('paragraph_block', wagtail.wagtailcore.blocks.RichTextBlock(icon='fa-paragraph', template='blocks/paragraph_block.html')), ('image_block', wagtail.wagtailcore.blocks.StructBlock((('image', wagtail.wagtailimages.blocks.ImageChooserBlock(required=True)), ('caption', wagtail.wagtailcore.blocks.CharBlock(required=False)), ('attribution', wagtail.wagtailcore.blocks.CharBlock(required=False))))), ('block_quote', wagtail.wagtailcore.blocks.StructBlock((('attribute_name', wagtail.wagtailcore.blocks.CharBlock(blank=True, label='e.g. Guy Picciotto', required=False)),))), ('embed_block', wagtail.wagtailembeds.blocks.EmbedBlock(help_text='Insert an embed URL e.g https://www.youtube.com/embed/SGJFWirQ3ks', icon='fa-s15', template='blocks/embed_block.html'))), blank=True, verbose_name='Blog post')),
+                ('image', models.ForeignKey(blank=True, help_text='Location image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='BlogPageTag',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('content_object', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='tagged_items', to='blog.BlogPage')),
+                ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blog_blogpagetag_items', to='taggit.Tag')),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='BlogPeopleRelationship',
+            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', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='blog_person_relationship', to='blog.BlogPage')),
+                ('people', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person_blog_relationship', to='base.People')),
+            ],
+            options={
+                'abstract': False,
+                'ordering': ['sort_order'],
+            },
+        ),
+        migrations.AddField(
+            model_name='blogpage',
+            name='tags',
+            field=modelcluster.contrib.taggit.ClusterTaggableManager(blank=True, help_text='A comma-separated list of tags.', through='blog.BlogPageTag', to='taggit.Tag', verbose_name='Tags'),
+        ),
+    ]

+ 77 - 32
bakerydemo/blog/models.py

@@ -1,30 +1,31 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
+from django.contrib import messages
 from django.db import models
 from django.db import models
+from django.shortcuts import redirect, render
 
 
-from modelcluster.fields import ParentalKey
 from modelcluster.contrib.taggit import ClusterTaggableManager
 from modelcluster.contrib.taggit import ClusterTaggableManager
-from taggit.models import TaggedItemBase
-from wagtail.wagtailcore.models import Page, Orderable, Collection
-from wagtail.wagtailsearch import index
-from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
-from wagtail.wagtailcore.fields import StreamField, RichTextField
+from modelcluster.fields import ParentalKey
+
+from taggit.models import Tag, TaggedItemBase
+
+from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin, route
 from wagtail.wagtailadmin.edit_handlers import (
 from wagtail.wagtailadmin.edit_handlers import (
-        FieldPanel,
-        InlinePanel,
-        FieldRowPanel,
-        StreamFieldPanel,
-        MultiFieldPanel
-        )
+    FieldPanel, InlinePanel, StreamFieldPanel,
+)
+from wagtail.wagtailcore.fields import StreamField
+from wagtail.wagtailcore.models import Page, Orderable
+from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
+from wagtail.wagtailsearch import index
 from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
 from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
+
 from bakerydemo.base.blocks import BaseStreamBlock
 from bakerydemo.base.blocks import BaseStreamBlock
 
 
 
 
 class BlogPeopleRelationship(Orderable, models.Model):
 class BlogPeopleRelationship(Orderable, models.Model):
     """
     """
-    This defines the relationship between the `LocationPage` within the `locations`
-    app and the About page below allowing us to add locations to the about
-    section.
+    This defines the relationship between the `People` within the `base`
+    app and the BlogPage below allowing us to add people to a BlogPage.
     """
     """
     page = ParentalKey(
     page = ParentalKey(
         'BlogPage', related_name='blog_person_relationship'
         'BlogPage', related_name='blog_person_relationship'
@@ -43,7 +44,7 @@ class BlogPageTag(TaggedItemBase):
 
 
 class BlogPage(Page):
 class BlogPage(Page):
     """
     """
-    The About Page
+    A Blog Page (Post)
     """
     """
     image = models.ForeignKey(
     image = models.ForeignKey(
         'wagtailimages.Image',
         'wagtailimages.Image',
@@ -59,8 +60,8 @@ class BlogPage(Page):
     date_published = models.DateField("Date article published", blank=True, null=True)
     date_published = models.DateField("Date article published", blank=True, null=True)
 
 
     body = StreamField(
     body = StreamField(
-        BaseStreamBlock(), verbose_name="About page detail", blank=True
-        )
+        BaseStreamBlock(), verbose_name="Blog post", blank=True
+    )
 
 
     content_panels = Page.content_panels + [
     content_panels = Page.content_panels + [
         ImageChooserPanel('image'),
         ImageChooserPanel('image'),
@@ -72,20 +73,37 @@ class BlogPage(Page):
         FieldPanel('tags'),
         FieldPanel('tags'),
     ]
     ]
 
 
+    search_fields = Page.search_fields + [
+        index.SearchField('title'),
+        index.SearchField('body'),
+    ]
+
     def authors(self):
     def authors(self):
+        """
+        Returns the BlogPage's related People
+        """
         authors = [
         authors = [
-             n.people for n in self.blog_person_relationship.all()
+            n.people for n in self.blog_person_relationship.all()
         ]
         ]
 
 
         return authors
         return authors
 
 
-    # def tags(self):
-    #     tags = self.tags.all()
-    #     return tags
-
-    parent_page_types = [
-       'BlogIndexPage'
-    ]
+    @property
+    def get_tags(self):
+        """
+        Returns the BlogPage's related list of Tags.
+        Each Tag is modified to include a url attribute
+        """
+        tags = self.tags.all()
+        for tag in tags:
+            tag.url = '/'+'/'.join(s.strip('/') for s in [
+                self.get_parent().url,
+                'tags',
+                tag.slug
+            ])
+        return tags
+
+    parent_page_types = ['BlogIndexPage']
 
 
     # Defining what content type can sit under the parent
     # Defining what content type can sit under the parent
     # The empty array means that no children can be placed under the
     # The empty array means that no children can be placed under the
@@ -95,10 +113,14 @@ class BlogPage(Page):
     # api_fields = ['image', 'body']
     # api_fields = ['image', 'body']
 
 
 
 
-class BlogIndexPage(Page):
-    """
+class BlogIndexPage(RoutablePageMixin, Page):
     """
     """
+    Index page for blogs.
+    We need to alter the page model's context to return the child page objects - the
+    BlogPage - so that it works as an index page
 
 
+    The RoutablePageMixin is used to allow for a custom sub-URL
+    """
     image = models.ForeignKey(
     image = models.ForeignKey(
         'wagtailimages.Image',
         'wagtailimages.Image',
         null=True,
         null=True,
@@ -123,14 +145,37 @@ class BlogIndexPage(Page):
 
 
     # Defining what content type can sit under the parent. Since it's a blank
     # Defining what content type can sit under the parent. Since it's a blank
     # array no subpage can be added
     # array no subpage can be added
-    subpage_types = [
-        'BlogPage'
-    ]
+    subpage_types = ['BlogPage']
 
 
     def get_context(self, request):
     def get_context(self, request):
         context = super(BlogIndexPage, self).get_context(request)
         context = super(BlogIndexPage, self).get_context(request)
-        context['posts'] = BlogPage.objects.descendant_of(
+        context['blogs'] = BlogPage.objects.descendant_of(
             self).live().order_by(
             self).live().order_by(
             '-first_published_at')
             '-first_published_at')
         return context
         return context
+
+    @route('^tags/$', name='tag_archive')
+    @route('^tags/(\w+)/$', name='tag_archive')
+    def tag_archive(self, request, tag=None):
+        """
+        A Custom view that utilizes Tags. This view will
+        return all related BlogPages for a given Tag or redirect back to
+        the BlogIndexPage
+        """
+        try:
+            tag = Tag.objects.get(slug=tag)
+        except Tag.DoesNotExist:
+            if tag:
+                msg = 'There are no blog posts tagged with "{}"'.format(tag)
+                messages.add_message(request, messages.INFO, msg)
+            return redirect(self.url)
+
+        blogs = BlogPage.objects.filter(tags=tag).live().descendant_of(self)
+
+        context = {
+            'title': 'Posts tagged with: {}'.format(tag.name),
+            'blogs': blogs
+        }
+        return render(request, 'blog/blog_index_page.html', context)
+
     # api_fields = ['introduction']
     # api_fields = ['introduction']

+ 77 - 0
bakerydemo/breads/migrations/0001_initial.py

@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 12:17
+from __future__ import unicode_literals
+
+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 = [
+        ('wagtailcore', '0032_add_bulk_delete_page_permission'),
+        ('wagtailimages', '0017_reduce_focal_point_key_max_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='BreadPage',
+            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')),
+                ('description', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='full title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock())))),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='BreadsIndexPage',
+            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='BreadType',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=255)),
+            ],
+            options={
+                'verbose_name_plural': 'Bread types',
+            },
+        ),
+        migrations.CreateModel(
+            name='Country',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('title', models.CharField(max_length=100)),
+            ],
+            options={
+                'verbose_name_plural': 'Countries of Origin',
+            },
+        ),
+        migrations.AddField(
+            model_name='breadpage',
+            name='bread_type',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='breads.BreadType'),
+        ),
+        migrations.AddField(
+            model_name='breadpage',
+            name='image',
+            field=models.ForeignKey(blank=True, help_text='Landscape mode only; horizontal width between 1000px and 3000px.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image'),
+        ),
+        migrations.AddField(
+            model_name='breadpage',
+            name='origin',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='breads.Country'),
+        ),
+    ]

+ 16 - 16
bakerydemo/breads/models.py

@@ -1,21 +1,21 @@
 from django.db import models
 from django.db import models
 
 
-from wagtail.wagtailcore.models import Page
-from wagtail.wagtailcore.fields import StreamField
 from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel
 from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel
+from wagtail.wagtailcore.fields import StreamField
+from wagtail.wagtailcore.models import Page
 
 
+from wagtail.wagtailcore import blocks
 from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
 from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
 from wagtail.wagtailsearch import index
 from wagtail.wagtailsearch import index
-from wagtail.wagtailcore import blocks
 from wagtail.wagtailsnippets.models import register_snippet
 from wagtail.wagtailsnippets.models import register_snippet
 
 
 
 
 @register_snippet
 @register_snippet
 class Country(models.Model):
 class Country(models.Model):
-    '''
+    """
     Standard Django model to store set of countries of origin.
     Standard Django model to store set of countries of origin.
     Exposed in the Wagtail admin via Snippets.
     Exposed in the Wagtail admin via Snippets.
-    '''
+    """
 
 
     title = models.CharField(max_length=100)
     title = models.CharField(max_length=100)
 
 
@@ -28,9 +28,9 @@ class Country(models.Model):
 
 
 @register_snippet
 @register_snippet
 class BreadType(models.Model):
 class BreadType(models.Model):
-    '''
+    """
     Standard Django model used as a Snippet in the BreadPage model.
     Standard Django model used as a Snippet in the BreadPage model.
-    '''
+    """
 
 
     title = models.CharField(max_length=255)
     title = models.CharField(max_length=255)
 
 
@@ -46,16 +46,16 @@ class BreadType(models.Model):
 
 
 
 
 class BreadPage(Page):
 class BreadPage(Page):
-    '''
+    """
     Detail view for a specific bread
     Detail view for a specific bread
-    '''
+    """
 
 
     origin = models.ForeignKey(
     origin = models.ForeignKey(
         Country,
         Country,
         on_delete=models.SET_NULL,
         on_delete=models.SET_NULL,
         null=True,
         null=True,
         blank=True,
         blank=True,
-        )
+    )
     description = StreamField([
     description = StreamField([
         ('heading', blocks.CharBlock(classname="full title")),
         ('heading', blocks.CharBlock(classname="full title")),
         ('paragraph', blocks.RichTextBlock()),
         ('paragraph', blocks.RichTextBlock()),
@@ -89,17 +89,17 @@ class BreadPage(Page):
         index.SearchField('description'),
         index.SearchField('description'),
     ]
     ]
 
 
-    parent_page_types = [
-       'BreadsIndexPage'
-    ]
+    parent_page_types = ['BreadsIndexPage']
+
+    api_fields = ['title', 'bread_type', 'origin', 'image']
 
 
 
 
 class BreadsIndexPage(Page):
 class BreadsIndexPage(Page):
-    '''
+    """
     Index page for breads. We don't have any fields within our model but we need
     Index page for breads. We don't have any fields within our model but we need
-    to alter the page models context to return the child page objects - the
+    to alter the page model's context to return the child page objects - the
     BreadPage - so that it works as an index page
     BreadPage - so that it works as an index page
-    '''
+    """
 
 
     subpage_types = ['BreadPage']
     subpage_types = ['BreadPage']
 
 

+ 62 - 0
bakerydemo/locations/migrations/0001_initial.py

@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.5 on 2017-02-10 12:17
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('wagtailcore', '0032_add_bulk_delete_page_permission'),
+        ('wagtailimages', '0017_reduce_focal_point_key_max_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='LocationOperatingHours',
+            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)),
+                ('day', models.CharField(choices=[('MON', 'MON'), ('TUE', 'TUE'), ('WED', 'WED'), ('THU', 'THU'), ('FRI', 'FRI'), ('SAT', 'SAT'), ('SUN', 'SUN')], default='MON', max_length=3)),
+                ('opening_time', models.TimeField()),
+                ('closing_time', models.TimeField()),
+            ],
+            options={
+                'abstract': False,
+                'ordering': ['sort_order'],
+            },
+        ),
+        migrations.CreateModel(
+            name='LocationPage',
+            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')),
+                ('address', models.TextField()),
+                ('lat_long', models.CharField(help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182)                    Right click Google Maps and click 'What's Here'", max_length=36)),
+                ('image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
+            ],
+            options={
+                'abstract': False,
+            },
+            bases=('wagtailcore.page',),
+        ),
+        migrations.CreateModel(
+            name='LocationsIndexPage',
+            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.AddField(
+            model_name='locationoperatinghours',
+            name='location',
+            field=modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='hours_of_operation', to='locations.LocationPage'),
+        ),
+    ]

+ 16 - 14
bakerydemo/locations/models.py

@@ -9,9 +9,9 @@ from wagtail.wagtailsearch import index
 
 
 
 
 class OperatingHours(models.Model):
 class OperatingHours(models.Model):
-    '''
+    """
     Django model to capture operating hours for a Location
     Django model to capture operating hours for a Location
-    '''
+    """
     MONDAY = 'MON'
     MONDAY = 'MON'
     TUESDAY = 'TUE'
     TUESDAY = 'TUE'
     WEDNESDAY = 'WED'
     WEDNESDAY = 'WED'
@@ -52,9 +52,9 @@ class OperatingHours(models.Model):
 
 
 
 
 class LocationOperatingHours(Orderable, OperatingHours):
 class LocationOperatingHours(Orderable, OperatingHours):
-    '''
+    """
     Operating Hours entry for a Location
     Operating Hours entry for a Location
-    '''
+    """
     location = ParentalKey(
     location = ParentalKey(
         'LocationPage',
         'LocationPage',
         related_name='hours_of_operation'
         related_name='hours_of_operation'
@@ -62,9 +62,9 @@ class LocationOperatingHours(Orderable, OperatingHours):
 
 
 
 
 class LocationsIndexPage(Page):
 class LocationsIndexPage(Page):
-    '''
+    """
     Index page for locations
     Index page for locations
-    '''
+    """
 
 
     subpage_types = ['LocationPage']
     subpage_types = ['LocationPage']
 
 
@@ -77,9 +77,9 @@ class LocationsIndexPage(Page):
 
 
 
 
 class LocationPage(Page):
 class LocationPage(Page):
-    '''
+    """
     Detail for a specific location
     Detail for a specific location
-    '''
+    """
 
 
     address = models.TextField()
     address = models.TextField()
     image = models.ForeignKey(
     image = models.ForeignKey(
@@ -93,8 +93,6 @@ class LocationPage(Page):
         max_length=36,
         max_length=36,
         help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182) \
         help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182) \
                    Right click Google Maps and click 'What\'s Here'"
                    Right click Google Maps and click 'What\'s Here'"
-
-
     )
     )
 
 
     # Search index configuration
     # Search index configuration
@@ -111,12 +109,16 @@ class LocationPage(Page):
     ]
     ]
 
 
     def __str__(self):
     def __str__(self):
-        return self.name
+        return self.title
 
 
     def opening_hours(self):
     def opening_hours(self):
         hours = self.hours_of_operation.all()
         hours = self.hours_of_operation.all()
         return hours
         return hours
 
 
-    parent_page_types = [
-       'LocationsIndexPage'
-    ]
+    def get_context(self, request):
+        context = super(LocationPage, self).get_context(request)
+        context['lat'] = self.lat_long.split(",")[0]
+        context['long'] = self.lat_long.split(",")[1]
+        return context
+
+    parent_page_types = ['LocationsIndexPage']

+ 0 - 0
bakerydemo/search/__init__.py


+ 57 - 0
bakerydemo/search/views.py

@@ -0,0 +1,57 @@
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
+from django.shortcuts import render
+
+from wagtail.wagtailcore.models import Page
+from wagtail.wagtailsearch.models import Query
+
+from bakerydemo.blog.models import BlogPage
+from bakerydemo.breads.models import BreadPage
+from bakerydemo.locations.models import LocationPage
+
+
+def search(request):
+    # Search
+    search_query = request.GET.get('query', None)
+    if search_query:
+        """
+        Because we can't use ElasticSearch for the demo, we use the native db search.
+        But native DB search can't search specific fields in our models on a `Page` query.
+        So for demo purposes ONLY, we hard-code in the model names we want to search.
+        In production, use ElasticSearch and a simplified search query, per
+        http://docs.wagtail.io/en/v1.8.1/topics/search/searching.html
+        """
+
+        blog_results = BlogPage.objects.live().search(search_query)
+        blog_page_ids = [p.page_ptr.id for p in blog_results]
+
+        bread_results = BreadPage.objects.live().search(search_query)
+        bread_page_ids = [p.page_ptr.id for p in bread_results]
+
+        location_results = LocationPage.objects.live().search(search_query)
+        location_result_ids = [p.page_ptr.id for p in location_results]
+
+        page_ids = blog_page_ids + bread_page_ids + location_result_ids
+        search_results = Page.objects.live().filter(id__in=page_ids)
+
+        query = Query.get(search_query)
+
+        # Record hit
+        query.add_hit()
+
+    else:
+        search_results = Page.objects.none()
+
+    # Pagination
+    page = request.GET.get('page', 1)
+    paginator = Paginator(search_results, 10)
+    try:
+        search_results = paginator.page(page)
+    except PageNotAnInteger:
+        search_results = paginator.page(1)
+    except EmptyPage:
+        search_results = paginator.page(paginator.num_pages)
+
+    return render(request, 'search/search_results.html', {
+        'search_query': search_query,
+        'search_results': search_results,
+    })

+ 5 - 2
bakerydemo/settings/base.py

@@ -35,7 +35,7 @@ INSTALLED_APPS = [
     'bakerydemo.blog',
     'bakerydemo.blog',
     'bakerydemo.breads',
     'bakerydemo.breads',
     'bakerydemo.locations',
     'bakerydemo.locations',
-    # 'bakerydemo.search',
+    'bakerydemo.search',
 
 
     'wagtail.contrib.wagtailsearchpromotions',
     'wagtail.contrib.wagtailsearchpromotions',
     'wagtail.wagtailforms',
     'wagtail.wagtailforms',
@@ -50,7 +50,9 @@ INSTALLED_APPS = [
     'wagtail.wagtailadmin',
     'wagtail.wagtailadmin',
     'wagtail.contrib.modeladmin',
     'wagtail.contrib.modeladmin',
     'wagtail.wagtailcore',
     'wagtail.wagtailcore',
-
+    'wagtail.contrib.wagtailapi',
+    
+    'rest_framework',
     'modelcluster',
     'modelcluster',
     'taggit',
     'taggit',
     'wagtailfontawesome',
     'wagtailfontawesome',
@@ -168,6 +170,7 @@ WAGTAILSEARCH_BACKENDS = {
     },
     },
 }
 }
 
 
+
 # Wagtail settings
 # Wagtail settings
 
 
 WAGTAIL_SITE_NAME = "bakerydemo"
 WAGTAIL_SITE_NAME = "bakerydemo"

+ 2 - 10
bakerydemo/settings/dev.py

@@ -1,19 +1,11 @@
 from .base import *
 from .base import *
 
 
-
-# SECURITY WARNING: don't run with debug turned on in production!
 DEBUG = True
 DEBUG = True
-TEMPLATES[0]['OPTIONS']['debug'] = True
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'CHANGEME!!!'
-
-INTERNAL_IPS = ('127.0.0.1', '10.0.2.2')
-
-BASE_URL = 'http://localhost:8000'
 
 
 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
 EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
 
 
+# BASE_URL required for notification emails
+BASE_URL = 'http://localhost:8000'
 
 
 try:
 try:
     from .local import *
     from .local import *

+ 0 - 175
bakerydemo/settings/production.py

@@ -1,175 +0,0 @@
-import os
-import dj_database_url
-
-from .base import *
-
-# Do not set SECRET_KEY, Postgres or LDAP password or any other sensitive data here.
-# Instead, use environment variables or create a local.py file on the server.
-
-# Disable debug mode
-DEBUG = False
-TEMPLATES[0]['OPTIONS']['debug'] = False
-
-
-# Configuration from environment variables
-# Alternatively, you can set these in a local.py file on the server
-
-env = os.environ.copy()
-
-# On Torchbox servers, many environment variables are prefixed with "CFG_"
-for key, value in os.environ.items():
-    if key.startswith('CFG_'):
-        env[key[4:]] = value
-
-
-# Basic configuration
-
-APP_NAME = env.get('APP_NAME', 'bakerydemo')
-
-if 'SECRET_KEY' in env:
-    SECRET_KEY = env['SECRET_KEY']
-
-if 'ALLOWED_HOSTS' in env:
-    ALLOWED_HOSTS = env['ALLOWED_HOSTS'].split(',')
-
-if 'PRIMARY_HOST' in env:
-    BASE_URL = 'http://%s/' % env['PRIMARY_HOST']
-
-if 'SERVER_EMAIL' in env:
-    SERVER_EMAIL = env['SERVER_EMAIL']
-
-if 'CACHE_PURGE_URL' in env:
-    INSTALLED_APPS += ( 'wagtail.contrib.wagtailfrontendcache', )
-    WAGTAILFRONTENDCACHE = {
-        'default': {
-            'BACKEND': 'wagtail.contrib.wagtailfrontendcache.backends.HTTPBackend',
-            'LOCATION': env['CACHE_PURGE_URL'],
-        },
-    }
-
-if 'STATIC_URL' in env:
-    STATIC_URL = env['STATIC_URL']
-
-if 'STATIC_DIR' in env:
-    STATIC_ROOT = env['STATIC_DIR']
-
-if 'MEDIA_URL' in env:
-    MEDIA_URL = env['MEDIA_URL']
-
-if 'MEDIA_DIR' in env:
-    MEDIA_ROOT = env['MEDIA_DIR']
-
-
-# Database
-
-if 'DATABASE_URL' in os.environ:
-    DATABASES = {'default': dj_database_url.config()}
-
-else:
-    DATABASES = {
-        'default': {
-            'ENGINE': 'django.db.backends.postgresql_psycopg2',
-            'NAME': env.get('PGDATABASE', APP_NAME),
-            'CONN_MAX_AGE': 600,  # number of seconds database connections should persist for
-
-            # User, host and port can be configured by the PGUSER, PGHOST and
-            # PGPORT environment variables (these get picked up by libpq).
-        }
-    }
-
-
-# Elasticsearch
-
-if 'ELASTICSEARCH_URL' in env:
-    WAGTAILSEARCH_BACKENDS = {
-        'default': {
-            'BACKEND': 'wagtail.wagtailsearch.backends.elasticsearch.ElasticSearch',
-            'URLS': [env['ELASTICSEARCH_URL']],
-            'INDEX': APP_NAME,
-            'ATOMIC_REBUILD': True,
-        },
-    }
-
-
-# Logging
-
-LOGGING = {
-    'version': 1,
-    'disable_existing_loggers': False,
-    'handlers': {
-        'mail_admins': {
-            'level': 'ERROR',
-            'class': 'django.utils.log.AdminEmailHandler',
-        },
-    },
-    'formatters': {
-        'default': {
-            'verbose': '[%(asctime)s] (%(process)d/%(thread)d) %(name)s %(levelname)s: %(message)s'
-        }
-    },
-    'loggers': {
-        'bakerydemo': {
-            'handlers':     [],
-            'level':        'INFO',
-            'propagate':    False,
-            'formatter':    'verbose',
-        },
-        'wagtail': {
-            'handlers':     [],
-            'level':        'INFO',
-            'propagate':    False,
-            'formatter':    'verbose',
-        },
-        'django.request': {
-            'handlers':     ['mail_admins'],
-            'level':        'ERROR',
-            'propagate':    False,
-            'formatter':    'verbose',
-        },
-        'django.security': {
-            'handlers':     ['mail_admins'],
-            'level':        'ERROR',
-            'propagate':    False,
-            'formatter':    'verbose',
-        },
-    },
-}
-
-
-if 'LOG_DIR' in env:
-    # bakerydemo log
-    LOGGING['handlers']['bakerydemo_file'] = {
-        'level':        'INFO',
-        'class':        'cloghandler.ConcurrentRotatingFileHandler',
-        'filename':     os.path.join(env['LOG_DIR'], 'bakerydemo.log'),
-        'maxBytes':     5242880, # 5MB
-        'backupCount':  5
-    }
-    LOGGING['loggers']['wagtail']['handlers'].append('bakerydemo_file')
-
-    # Wagtail log
-    LOGGING['handlers']['wagtail_file'] = {
-        'level':        'INFO',
-        'class':        'cloghandler.ConcurrentRotatingFileHandler',
-        'filename':     os.path.join(env['LOG_DIR'], 'wagtail.log'),
-        'maxBytes':     5242880, # 5MB
-        'backupCount':  5
-    }
-    LOGGING['loggers']['wagtail']['handlers'].append('wagtail_file')
-
-    # Error log
-    LOGGING['handlers']['errors_file'] = {
-        'level':        'ERROR',
-        'class':        'cloghandler.ConcurrentRotatingFileHandler',
-        'filename':     os.path.join(env['LOG_DIR'], 'error.log'),
-        'maxBytes':     5242880, # 5MB
-        'backupCount':  5
-    }
-    LOGGING['loggers']['django.request']['handlers'].append('errors_file')
-    LOGGING['loggers']['django.security']['handlers'].append('errors_file')
-
-
-try:
-    from .local import *
-except ImportError:
-    pass

+ 4 - 4
bakerydemo/templates/about_page.html

@@ -2,12 +2,12 @@
 {% load wagtailimages_tags %}
 {% load wagtailimages_tags %}
 
 
 {% block content %}
 {% block content %}
-	{{ page.title }}
+    {{ page.title }}
 
 
     <div class="image">
     <div class="image">
         {% image page.image width-500 as photo %}
         {% image page.image width-500 as photo %}
               <img src="{{ photo.url }}" width="{{ photo.width }}" height="{{ photo.height }}" alt="{{ photo.alt }}" />
               <img src="{{ photo.url }}" width="{{ photo.width }}" height="{{ photo.height }}" alt="{{ photo.alt }}" />
-	</div>
+    </div>
 
 
-	{{ page.body }}
-{% endblock content %}
+    {{ page.body }}
+{% endblock content %}

+ 12 - 2
bakerydemo/templates/base.html

@@ -20,7 +20,7 @@
                 {% endif %}
                 {% endif %}
             {% endblock %}
             {% endblock %}
         </title>
         </title>
-        <meta name="description" content="">
+        <meta name="description" content="{% if self.search_description %}{{ self.search_description }}{% endif %}">
         <meta name="viewport" content="width=device-width, initial-scale=1">
         <meta name="viewport" content="width=device-width, initial-scale=1">
 
 
         <!-- Bootstrap Core CSS -->
         <!-- Bootstrap Core CSS -->
@@ -69,6 +69,16 @@
         </div>
         </div>
     </div>
     </div>
 
 
+    {% if messages %}
+    <div>
+        <ul class="messages">
+            {% for message in messages %}
+            <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
+            {% endfor %}
+        </ul>
+    </div>
+    {% endif %}
+
     <div class="container">
     <div class="container">
         <div class="row">
         <div class="row">
             <div class="col-lg-7 col-lg-offset-2">
             <div class="col-lg-7 col-lg-offset-2">
@@ -137,7 +147,7 @@
                             </a>
                             </a>
                         </li>
                         </li>
                     </ul>
                     </ul>
-                    <p class="copyright text-center text-muted">Copyright &copy; Your Website 2016</p>
+                    <p class="copyright text-center text-muted">{% get_footer_text %}</p>
                 </div>
                 </div>
             </div>
             </div>
         </div>
         </div>

+ 1 - 1
bakerydemo/templates/base/about_page.html

@@ -10,4 +10,4 @@
     </div>
     </div>
 
 
     {{ page.body }}
     {{ page.body }}
-{% endblock content %}
+{% endblock content %}

+ 11 - 14
bakerydemo/templates/base/form_page.html

@@ -1,15 +1,12 @@
+{% extends "base.html" %}
 {% load wagtailcore_tags %}
 {% load wagtailcore_tags %}
-<html>
-    <head>
-        <title>{{ page.title }}</title>
-    </head>
-    <body>
-        <h1>{{ page.title }}</h1>
-        {{ page.intro|richtext }}
-        <form action="{% pageurl page %}" method="POST">
-            {% csrf_token %}
-            {{ form.as_p }}
-            <input type="submit">
-        </form>
-    </body>
-</html>
+
+{% block content %}
+    {{ page.intro|richtext }}
+
+    <form action="{% pageurl page %}" method="POST">
+        {% csrf_token %}
+        {{ form.as_p }}
+        <input type="submit">
+    </form>
+{% endblock content %}

+ 7 - 9
bakerydemo/templates/base/form_page_landing.html

@@ -1,9 +1,7 @@
-<html>
-    <head>
-        <title>{{ page.title }}</title>
-    </head>
-    <body>
-        <h1>{{ page.title }}</h1>
-        <h2>Thanks for your submission!</h2>
-    </body>
-</html>
+{% extends "base.html" %}
+{% load wagtailcore_tags %}
+
+{% block content %}
+    <h1>{{ page.title }}</h1>
+    {{ page.thank_you_text|richtext }}
+{% endblock content %}

+ 4 - 0
bakerydemo/templates/base/include/footer.html

@@ -0,0 +1,4 @@
+{% load wagtailcore_tags %}
+
+
+{{ footer_text|richtext }}

+ 8 - 8
bakerydemo/templates/blocks/heading_block.html

@@ -1,15 +1,15 @@
 {% if self.size == 'h2' %}
 {% if self.size == 'h2' %}
-	    <h2>{{ self.heading_text }}</h2>
+        <h2>{{ self.heading_text }}</h2>
 
 
-	{% elif self.size == 'h3' %}
-	    <h3>{{ self.heading_text }}</h3>
+    {% elif self.size == 'h3' %}
+        <h3>{{ self.heading_text }}</h3>
 
 
-	{% elif self.size == 'h4' %}
-	    <h4>{{ self.heading_text }}</h4>
+    {% elif self.size == 'h4' %}
+        <h4>{{ self.heading_text }}</h4>
 
 
 {% endif %}
 {% endif %}
 
 
 {% comment %}
 {% comment %}
-	Content is coming from the StandardBlock StreamField
-	class within `blocks.py`
-{% endcomment %}
+    Content is coming from the StandardBlock StreamField
+    class within `blocks.py`
+{% endcomment %}

+ 2 - 4
bakerydemo/templates/blocks/image_block.html

@@ -1,8 +1,6 @@
 {% load wagtailimages_tags %}
 {% load wagtailimages_tags %}
 
 
-
-
 <figure>
 <figure>
-  {% image self.image fill-600x600 %}
-      <figcaption>{{ self.caption }} - {{ self.attribution }}</figcaption>
+    {% image self.image fill-600x600 %}
+    <figcaption>{{ self.caption }} - {{ self.attribution }}</figcaption>
 </figure>
 </figure>

+ 1 - 1
bakerydemo/templates/blocks/paragraph_block.html

@@ -1 +1 @@
-        {{ self }}
+{{ self }}

+ 9 - 3
bakerydemo/templates/blog/blog_index_page.html

@@ -3,8 +3,14 @@
 
 
 {% block content %}
 {% block content %}
     {{ page.title }}
     {{ page.title }}
+    {% for blog in blogs %}
+        <div>
+            <h2><a href="{{ blog.full_url }}">{{ blog.title }}</a></h2>
+            {{ blog.body|truncatewords_html:10 }}
 
 
-    {% for post in posts %}
-        <div><a href="{{ post.slug }}">{{ post.title }}</a></div>
+            {% for tag in blog.get_tags  %}
+                <a href="{{ tag.url }}">{{ tag }}</a>
+            {% endfor %}
+        </div>
     {% endfor %}
     {% endfor %}
-{% endblock content %}
+{% endblock content %}

+ 4 - 4
bakerydemo/templates/blog/blog_page.html

@@ -7,15 +7,15 @@
       {% image self.image fill-600x600 %}
       {% image self.image fill-600x600 %}
     </figure>
     </figure>
 
 
-    {% for tag in page.tags.all  %}
-        {{ tag }}
+    {% for tag in page.get_tags  %}
+        <a href="{{ tag.url }}">{{ tag }}</a>
     {% endfor %}
     {% endfor %}
 
 
     <date>{{ page.date_published }}</date>
     <date>{{ page.date_published }}</date>
-    
+
     {% for author in page.authors %}
     {% for author in page.authors %}
         <li>{{ author }}</li>
         <li>{{ author }}</li>
     {% endfor %}
     {% endfor %}
 
 
     {{ page.body }}
     {{ page.body }}
-{% endblock content %}
+{% endblock content %}

+ 1 - 2
bakerydemo/templates/breads/breads_index_page.html

@@ -3,8 +3,7 @@
 
 
 {% block content %}
 {% block content %}
     {{ page.title }}
     {{ page.title }}
-
     {% for bread in breads %}
     {% for bread in breads %}
         <div><a href="{{ bread.slug }}">{{ bread.title }}</a></div>
         <div><a href="{{ bread.slug }}">{{ bread.title }}</a></div>
     {% endfor %}
     {% endfor %}
-{% endblock content %}
+{% endblock content %}

+ 38 - 2
bakerydemo/templates/locations/location_page.html

@@ -2,14 +2,50 @@
 {% load wagtailimages_tags %}
 {% load wagtailimages_tags %}
 
 
 {% block content %}
 {% block content %}
+
+    <style>
+      /* Needed for Google map embed */
+      #map {
+        height: 100%;
+      }
+      html, body {
+        height: 100%;
+        margin: 0;
+        padding: 0;
+      }
+    </style>
+
     <h1>{{ page.title }}</h1>
     <h1>{{ page.title }}</h1>
     <figure>
     <figure>
       {% image self.image fill-600x600 %}
       {% image self.image fill-600x600 %}
     </figure>
     </figure>
     <p>{{ page.address }}</p>
     <p>{{ page.address }}</p>
     <p>{{ page.lat_long }}</p>
     <p>{{ page.lat_long }}</p>
-    
+
+    <div id="map"></div>
+    <script>
+      var map;
+      function initMap() {
+        map = new google.maps.Map(document.getElementById('map'), {
+          center: {
+              lat: {{lat}},
+              lng: {{long}}
+          },
+          zoom: 8
+        });
+        var marker = new google.maps.Marker({
+          position: {
+              lat: {{lat}},
+              lng: {{long}}
+          },
+          map: map,
+          title: '{{page.title}}'
+        });
+      }
+    </script>
+    <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyD31CT9P9KxvNUJOwDq2kcFEIG8ADgaFgw&callback=initMap" async defer></script>
+
     {% for hours in page.opening_hours %}
     {% for hours in page.opening_hours %}
         <li>{{ hours }}</li>
         <li>{{ hours }}</li>
     {% endfor %}
     {% endfor %}
-{% endblock content %}
+{% endblock content %}

+ 14 - 0
bakerydemo/templates/search/search_box.html

@@ -0,0 +1,14 @@
+<form action="{% url 'search' %}" class="form-inline" method="get">
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="input-group">
+                <input type="text" name="query" class="form-control" />
+                <div class="input-group-btn">
+                    <button type="submit" class="btn btn-primary">
+                        <i class="glyphicon glyphicon-search"></i> Search
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+</form>

+ 33 - 0
bakerydemo/templates/search/search_results.html

@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% load wagtailcore_tags %}
+
+{% block title %}Search{% if search_results %} results{% endif %}{% endblock %}
+
+{% block content %}
+    <h1>
+        Search results{% if request.GET.query %} for “{{ request.GET.query }}”{% endif %}
+    </h1>
+    {% if search_results %}
+        <ul>
+            {% for result in search_results %}
+                <li>
+                    <h4>
+                        {% if result.specific.content_type.model == "blogpage" %}
+                            Blog post:
+                        {% elif result.specific.content_type.model == "locationpage" %}
+                            Location:
+                        {% else %}
+                            Bread
+                        {% endif %}
+                        <a href="{% pageurl result.specific %}">{{ result.specific }}</a>
+                    </h4>
+                        {% if result.specific.search_description %}{{ result.specific.search_description|safe }}{% endif %}
+                </li>
+            {% endfor %}
+        </ul>
+    {% elif search_query %}
+        No results found
+    {% else %}
+        You didn’t search for anything!
+    {% endif %}
+{% endblock %}

+ 0 - 4
bakerydemo/templates/tags/sidebar_menu.html

@@ -2,13 +2,10 @@
 {% get_site_root as site_root %}
 {% get_site_root as site_root %}
 
 
 {% if calling_page|has_protocol_parent and calling_page.content_type.model == 'standardpage' %}
 {% if calling_page|has_protocol_parent and calling_page.content_type.model == 'standardpage' %}
-
   <div class="off-canvas position-left reveal-for-large bla" id="offCanvasLeft" data-off-canvas>
   <div class="off-canvas position-left reveal-for-large bla" id="offCanvasLeft" data-off-canvas>
     {% protocol_menu calling_page=calling_page %}
     {% protocol_menu calling_page=calling_page %}
   </div>
   </div>
-
 {% elif ancestor.has_children %}
 {% elif ancestor.has_children %}
-
   <div class="off-canvas position-left reveal-for-large" id="offCanvasLeft" data-off-canvas>
   <div class="off-canvas position-left reveal-for-large" id="offCanvasLeft" data-off-canvas>
     <nav class="sidebar-nav">
     <nav class="sidebar-nav">
       <h3>In this section</h3>
       <h3>In this section</h3>
@@ -32,5 +29,4 @@
       </ul>
       </ul>
     </nav>
     </nav>
   </div>
   </div>
-
 {% endif %}
 {% endif %}

+ 1 - 2
bakerydemo/templates/tags/top_menu.html

@@ -1,7 +1,6 @@
 {% load navigation_tags wagtailcore_tags %}
 {% load navigation_tags wagtailcore_tags %}
 {% get_site_root as site_root %}
 {% get_site_root as site_root %}
 
 
-
 <nav class="navbar navbar-default navbar-custom">
 <nav class="navbar navbar-default navbar-custom">
     <div class="container-fluid">
     <div class="container-fluid">
         <ul class="nav navbar-nav">
         <ul class="nav navbar-nav">
@@ -19,4 +18,4 @@
         </ul>
         </ul>
       </div>
       </div>
     </div>
     </div>
-</nav>
+</nav>

+ 1 - 0
bakerydemo/templates/tags/top_menu_children.html

@@ -1,4 +1,5 @@
 {% load navigation_tags wagtailcore_tags %}
 {% load navigation_tags wagtailcore_tags %}
+
 <ul class="dropdown-menu">
 <ul class="dropdown-menu">
   {% for child in menuitems_children %}
   {% for child in menuitems_children %}
     <li><a href="{% pageurl child %}">{{ child.title }}</a></li>
     <li><a href="{% pageurl child %}">{{ child.title }}</a></li>

+ 4 - 2
bakerydemo/urls.py

@@ -6,8 +6,9 @@ from wagtail.wagtailadmin import urls as wagtailadmin_urls
 from wagtail.wagtaildocs import urls as wagtaildocs_urls
 from wagtail.wagtaildocs import urls as wagtaildocs_urls
 from wagtail.wagtailcore import urls as wagtail_urls
 from wagtail.wagtailcore import urls as wagtail_urls
 
 
-# from bakerydemo.search import views as search_views
+from bakerydemo.search import views as search_views
 
 
+from wagtail.contrib.wagtailapi import urls as wagtailapi_urls
 
 
 urlpatterns = [
 urlpatterns = [
     url(r'^django-admin/', include(admin.site.urls)),
     url(r'^django-admin/', include(admin.site.urls)),
@@ -15,7 +16,8 @@ urlpatterns = [
     url(r'^admin/', include(wagtailadmin_urls)),
     url(r'^admin/', include(wagtailadmin_urls)),
     url(r'^documents/', include(wagtaildocs_urls)),
     url(r'^documents/', include(wagtaildocs_urls)),
 
 
-    # url(r'^search/$', search_views.search, name='search'),
+    url(r'^search/$', search_views.search, name='search'),
+    url(r'^api/', include(wagtailapi_urls)),
 
 
 ]
 ]
 
 

+ 67 - 2
readme.md

@@ -1,2 +1,67 @@
-bakerydemo
-==================
+Wagtail demo project
+=======================
+
+This is a demonstration project for [Wagtail CMS](http://wagtail.io).
+
+*We do __not__ recommend using this project to start your own site*. This project is only to provide some examples of
+implementing common features, it is not an exemplar of Django or Wagtail best practice.
+
+If you're reasonably new to Python/Django, we suggest you run this project on a Virtual Machine using Vagrant, which
+helps  resolve common software dependency issues. However for more experienced developers, instructions to start this
+project without Vagrant follow below.
+
+Once you're familiar with the examples in this project and you want to start a real site, we strongly recommend running
+the ``wagtail start`` command in a fresh virtual environment, explained in the
+[Wagtail CMS Documentation](http://wagtail.readthedocs.org/en/latest/getting_started/).
+
+Setup with Vagrant
+------------------
+
+### Dependencies
+* [VirtualBox](https://www.virtualbox.org/)
+* [Vagrant 1.5+](http://www.vagrantup.com)
+
+### Installation
+Run the following commands:
+
+    git clone https://github.com/torchbox/bakerydemo.git
+    cd wagtaildemo
+    vagrant up
+    vagrant ssh
+      (then, within the SSH session:)
+    ./manage.py migrate
+    ./manage.py load_initial_data
+    ./manage.py runserver 0.0.0.0:8000
+
+The demo site will now be accessible at [http://localhost:8000/](http://localhost:8000/) and the Wagtail admin
+interface at [http://localhost:8000/admin/](http://localhost:8000/admin/).
+
+Log into the admin with the credentials ``admin / changeme``.
+
+Setup without Vagrant
+-----
+Don't want to set up a whole VM to try out Wagtail? No problem.
+
+### Dependencies
+* [PIP](https://github.com/pypa/pip)
+
+### Installation
+
+With PIP installed run the following commands:
+
+    git clone https://github.com/torchbox/bakerydemo.git
+    cd wagtaildemo
+    pip install -r requirements.txt
+    ./manage.py migrate
+    ./manage.py load_initial_data
+    ./manage.py runserver
+
+Log into the admin with the credentials ``admin / changeme``.
+
+### Note on demo search:
+
+Because we can't (easily) use ElasticSearch for this demo, we use wagtail's native DB search.
+However, native DB search can't search specific fields in our models on a generalized `Page` query.
+So for demo purposes ONLY, we hard-code the model names we want to search into `search.views`, which is
+not ideal. In production, use ElasticSearch and a simplified search query, per
+http://docs.wagtail.io/en/v1.8.1/topics/search/searching.html

+ 2 - 1
vagrant/provision.sh

@@ -32,8 +32,9 @@ su - vagrant -c "$PIP install -r $PROJECT_DIR/requirements.txt"
 chmod a+x $PROJECT_DIR/manage.py
 chmod a+x $PROJECT_DIR/manage.py
 
 
 
 
-# Run syncdb/migrate/update_index
+# Run syncdb/migrate/load_initial_data/update_index
 su - vagrant -c "$PYTHON $PROJECT_DIR/manage.py migrate --noinput && \
 su - vagrant -c "$PYTHON $PROJECT_DIR/manage.py migrate --noinput && \
+                 $PYTHON $PROJECT_DIR/manage.py load_initial_data && \
                  $PYTHON $PROJECT_DIR/manage.py update_index"
                  $PYTHON $PROJECT_DIR/manage.py update_index"
 
 
 
 

Some files were not shown because too many files changed in this diff