瀏覽代碼

Replace built-in SEO functionality with wagtailseo (#426)

Vince Salvino 3 年之前
父節點
當前提交
f17ab4bbd2
共有 37 個文件被更改,包括 244 次插入1273 次删除
  1. 0 4
      coderedcms/blocks/__init__.py
  2. 0 90
      coderedcms/blocks/metadata_blocks.py
  3. 38 0
      coderedcms/migrations/0022_auto_20210731_1853.py
  4. 120 184
      coderedcms/models/page_models.py
  5. 0 13
      coderedcms/models/tests/test_page_models.py
  6. 1 45
      coderedcms/models/wagtailsettings_models.py
  7. 1 0
      coderedcms/project_template/basic/project_name/settings/base.py
  8. 0 1
      coderedcms/project_template/basic/website/models.py
  9. 1 0
      coderedcms/project_template/sass/project_name/settings/base.py
  10. 0 1
      coderedcms/project_template/sass/website/models.py
  11. 0 165
      coderedcms/schema.py
  12. 2 2
      coderedcms/templates/coderedcms/blocks/article_block_card.html
  13. 2 9
      coderedcms/templates/coderedcms/blocks/embed_video_block.html
  14. 2 2
      coderedcms/templates/coderedcms/blocks/pagelist_article_media.html
  15. 1 7
      coderedcms/templates/coderedcms/blocks/rich_text_block.html
  16. 0 22
      coderedcms/templates/coderedcms/blocks/struct_data_action.json
  17. 0 6
      coderedcms/templates/coderedcms/blocks/struct_data_hours.json
  18. 0 50
      coderedcms/templates/coderedcms/includes/struct_data_article.json
  19. 0 58
      coderedcms/templates/coderedcms/includes/struct_data_event.json
  20. 0 80
      coderedcms/templates/coderedcms/includes/struct_data_org.json
  21. 1 1
      coderedcms/templates/coderedcms/pages/article_index_page.html
  22. 0 56
      coderedcms/templates/coderedcms/pages/article_page.amp.html
  23. 5 54
      coderedcms/templates/coderedcms/pages/article_page.html
  24. 3 11
      coderedcms/templates/coderedcms/pages/article_page.search.html
  25. 0 234
      coderedcms/templates/coderedcms/pages/base.amp.html
  26. 3 44
      coderedcms/templates/coderedcms/pages/base.html
  27. 5 8
      coderedcms/templates/coderedcms/pages/event_page.html
  28. 1 54
      coderedcms/templatetags/coderedcms_tags.py
  29. 1 0
      coderedcms/tests/settings.py
  30. 0 31
      coderedcms/tests/test_utils.py
  31. 0 1
      coderedcms/tests/testapp/models.py
  32. 0 35
      coderedcms/utils.py
  33. 0 1
      docs/advanced/advanced02.rst
  34. 54 2
      docs/releases/v0.21.0.rst
  35. 2 1
      setup.py
  36. 1 0
      tutorial/mysite/mysite/settings/base.py
  37. 0 1
      tutorial/mysite/website/models.py

+ 0 - 4
coderedcms/blocks/__init__.py

@@ -53,10 +53,6 @@ from .layout_blocks import (
     GridBlock,
     GridBlock,
     HeroBlock
     HeroBlock
 )
 )
-from .metadata_blocks import (  # noqa
-    OpenHoursBlock,
-    StructuredDataActionBlock
-)
 from .base_blocks import (  # noqa
 from .base_blocks import (  # noqa
     BaseBlock,
     BaseBlock,
     BaseLayoutBlock,
     BaseLayoutBlock,

+ 0 - 90
coderedcms/blocks/metadata_blocks.py

@@ -1,90 +0,0 @@
-"""
-JSON and meta-data blocks, primarily used for SEO purposes.
-"""
-
-import json
-from django import forms
-from django.utils.translation import gettext_lazy as _
-from wagtail.core import blocks
-
-from coderedcms import schema
-
-
-class OpenHoursValue(blocks.StructValue):
-    """
-    Renders selected days as a json list.
-    """
-    @property
-    def days_json(self):
-        """
-        Custom property to return days as json list instead of default python list.
-        """
-        return json.dumps(self['days'])
-
-
-class OpenHoursBlock(blocks.StructBlock):
-    """
-    Holds day and time combination for business open hours.
-    """
-    days = blocks.MultipleChoiceBlock(
-        required=True,
-        verbose_name=_('Days'),
-        help_text=_('For late night hours past 23:59, define each day in a separate block.'),
-        widget=forms.CheckboxSelectMultiple,
-        choices=(
-            ('Monday', _('Monday')),
-            ('Tuesday', _('Tuesday')),
-            ('Wednesday', _('Wednesday')),
-            ('Thursday', _('Thursday')),
-            ('Friday', _('Friday')),
-            ('Saturday', _('Saturday')),
-            ('Sunday', _('Sunday')),
-        ))
-    start_time = blocks.TimeBlock(verbose_name=_('Opening time'))
-    end_time = blocks.TimeBlock(verbose_name=_('Closing time'))
-
-    class Meta:
-        template = 'coderedcms/blocks/struct_data_hours.json'
-        label = _('Open Hours')
-        value_class = OpenHoursValue
-
-
-class StructuredDataActionBlock(blocks.StructBlock):
-    """
-    Action object from schema.org
-    """
-    action_type = blocks.ChoiceBlock(
-        verbose_name=_('Action Type'),
-        required=True,
-        choices=schema.SCHEMA_ACTION_CHOICES
-    )
-    target = blocks.URLBlock(verbose_name=_('Target URL'))
-    language = blocks.CharBlock(
-        verbose_name=_('Language'),
-        help_text=_(
-            'If the action is offered in multiple languages, create separate actions for each language.'),  # noqa
-        default='en-US'
-    )
-    result_type = blocks.ChoiceBlock(
-        required=False,
-        verbose_name=_('Result Type'),
-        help_text=_('Leave blank for OrderAction'),
-        choices=schema.SCHEMA_RESULT_CHOICES
-    )
-    result_name = blocks.CharBlock(
-        required=False,
-        verbose_name=_('Result Name'),
-        help_text=_('Example: "Reserve a table", "Book an appointment", etc.')
-    )
-    extra_json = blocks.RawHTMLBlock(
-        required=False,
-        verbose_name=_('Additional action markup'),
-        form_classname='monospace',
-        help_text=_(
-            "Additional JSON-LD inserted into the Action dictionary. Must be properties of https://schema.org/Action."  # noqa
-        )
-    )
-
-    class Meta:
-        template = 'coderedcms/blocks/struct_data_action.json'
-        label = _('Action')

+ 38 - 0
coderedcms/migrations/0022_auto_20210731_1853.py

@@ -0,0 +1,38 @@
+# Generated by Django 3.1.13 on 2021-07-31 22:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('wagtailimages', '0023_add_choose_permissions'),
+        ('coderedcms', '0021_auto_20210730_1637'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='coderedpage',
+            name='canonical_url',
+            field=models.URLField(blank=True, help_text="Leave blank to use the page's URL.", max_length=255, verbose_name='Canonical URL'),
+        ),
+        migrations.AlterField(
+            model_name='coderedpage',
+            name='og_image',
+            field=models.ForeignKey(blank=True, help_text='Shown when linking to this page on social media.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image', verbose_name='Preview image'),
+        ),
+        migrations.AlterField(
+            model_name='coderedpage',
+            name='struct_org_geo_lat',
+            field=models.DecimalField(blank=True, decimal_places=8, max_digits=11, null=True, verbose_name='Geographic latitude'),
+        ),
+        migrations.AlterField(
+            model_name='coderedpage',
+            name='struct_org_geo_lng',
+            field=models.DecimalField(blank=True, decimal_places=8, max_digits=11, null=True, verbose_name='Geographic longitude'),
+        ),
+        migrations.DeleteModel(
+            name='SeoSettings',
+        ),
+    ]

+ 120 - 184
coderedcms/models/page_models.py

@@ -5,6 +5,10 @@ Base and abstract pages used in CodeRed CMS.
 import json
 import json
 import logging
 import logging
 import os
 import os
+import warnings
+from datetime import datetime
+from typing import Optional, TYPE_CHECKING
+
 import geocoder
 import geocoder
 from django import forms
 from django import forms
 from django.conf import settings
 from django.conf import settings
@@ -33,7 +37,6 @@ from modelcluster.tags import ClusterTaggableManager
 from pathlib import Path
 from pathlib import Path
 from taggit.models import TaggedItemBase
 from taggit.models import TaggedItemBase
 from wagtail.admin.edit_handlers import (
 from wagtail.admin.edit_handlers import (
-    HelpPanel,
     FieldPanel,
     FieldPanel,
     FieldRowPanel,
     FieldRowPanel,
     InlinePanel,
     InlinePanel,
@@ -55,15 +58,15 @@ from wagtail.contrib.forms.models import FormSubmission
 from wagtail.search import index
 from wagtail.search import index
 from wagtail.utils.decorators import cached_classmethod
 from wagtail.utils.decorators import cached_classmethod
 from wagtailcache.cache import WagtailCacheMixin
 from wagtailcache.cache import WagtailCacheMixin
+from wagtailseo.models import SeoMixin, TwitterCard
+from wagtailseo.utils import get_struct_data_images, StructDataEncoder
 
 
-from coderedcms import schema, utils
+from coderedcms import utils
 from coderedcms.blocks import (
 from coderedcms.blocks import (
     CONTENT_STREAMBLOCKS,
     CONTENT_STREAMBLOCKS,
     LAYOUT_STREAMBLOCKS,
     LAYOUT_STREAMBLOCKS,
     STREAMFORM_BLOCKS,
     STREAMFORM_BLOCKS,
     ContentWallBlock,
     ContentWallBlock,
-    OpenHoursBlock,
-    StructuredDataActionBlock,
 )
 )
 from coderedcms.fields import ColorField
 from coderedcms.fields import ColorField
 from coderedcms.forms import CoderedFormBuilder, CoderedSubmissionsListView
 from coderedcms.forms import CoderedFormBuilder, CoderedSubmissionsListView
@@ -72,7 +75,6 @@ from coderedcms.models.wagtailsettings_models import (
     GeneralSettings,
     GeneralSettings,
     GoogleApiSettings,
     GoogleApiSettings,
     LayoutSettings,
     LayoutSettings,
-    SeoSettings,
 )
 )
 from coderedcms.wagtail_flexible_forms.blocks import FormFieldBlock, FormStepBlock
 from coderedcms.wagtail_flexible_forms.blocks import FormFieldBlock, FormStepBlock
 from coderedcms.wagtail_flexible_forms.models import (
 from coderedcms.wagtail_flexible_forms.models import (
@@ -87,6 +89,10 @@ from coderedcms.settings import cr_settings
 from coderedcms.widgets import ClassifierSelectWidget
 from coderedcms.widgets import ClassifierSelectWidget
 
 
 
 
+if TYPE_CHECKING:
+    from wagtail.images.models import AbstractImage
+
+
 logger = logging.getLogger('coderedcms')
 logger = logging.getLogger('coderedcms')
 
 
 
 
@@ -100,8 +106,6 @@ def get_page_models():
 class CoderedPageMeta(PageBase):
 class CoderedPageMeta(PageBase):
     def __init__(cls, name, bases, dct):
     def __init__(cls, name, bases, dct):
         super().__init__(name, bases, dct)
         super().__init__(name, bases, dct)
-        if 'amp_template' not in dct:
-            cls.amp_template = None
         if 'search_db_include' not in dct:
         if 'search_db_include' not in dct:
             cls.search_db_include = False
             cls.search_db_include = False
         if 'search_db_boost' not in dct:
         if 'search_db_boost' not in dct:
@@ -124,7 +128,7 @@ class CoderedTag(TaggedItemBase):
     content_object = ParentalKey('coderedcms.CoderedPage', related_name='tagged_items')
     content_object = ParentalKey('coderedcms.CoderedPage', related_name='tagged_items')
 
 
 
 
-class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
+class CoderedPage(WagtailCacheMixin, SeoMixin, Page, metaclass=CoderedPageMeta):
     """
     """
     General use page with caching, templating, and SEO functionality.
     General use page with caching, templating, and SEO functionality.
     All pages should inherit from this.
     All pages should inherit from this.
@@ -139,7 +143,6 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
     # The page will render the following templates under certain conditions:
     # The page will render the following templates under certain conditions:
     #
     #
     # template = ''
     # template = ''
-    # amp_template = ''
     # ajax_template = ''
     # ajax_template = ''
     # search_template = ''
     # search_template = ''
 
 
@@ -213,120 +216,13 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
     )
     )
 
 
     ###############
     ###############
-    # SEO fields
+    # SEO overrides
     ###############
     ###############
 
 
-    og_image = models.ForeignKey(
-        get_image_model_string(),
-        null=True,
-        blank=True,
-        on_delete=models.SET_NULL,
-        related_name='+',
-        verbose_name=_('Open Graph preview image'),
-        help_text=_("The image shown when linking to this page on social media. If blank, defaults to article cover image, or logo in Settings > Layout > Logo"),  # noqa
-    )
-    struct_org_type = models.CharField(
-        default='',
-        blank=True,
-        max_length=255,
-        choices=schema.SCHEMA_ORG_CHOICES,
-        verbose_name=_('Organization type'),
-        help_text=_('If blank, no structured data will be used on this page.')
-    )
-    struct_org_name = models.CharField(
-        default='',
-        blank=True,
-        max_length=255,
-        verbose_name=_('Organization name'),
-        help_text=_('Leave blank to use the site name in Settings > Sites')
-    )
-    struct_org_logo = models.ForeignKey(
-        get_image_model_string(),
-        null=True,
-        blank=True,
-        on_delete=models.SET_NULL,
-        related_name='+',
-        verbose_name=_('Organization logo'),
-        help_text=_('Leave blank to use the logo in Settings > Layout > Logo')
-    )
-    struct_org_image = models.ForeignKey(
-        get_image_model_string(),
-        null=True,
-        blank=True,
-        on_delete=models.SET_NULL,
-        related_name='+',
-        verbose_name=_('Photo of Organization'),
-        help_text=_('A photo of the facility. This photo will be cropped to 1:1, 4:3, and 16:9 aspect ratios automatically.'),  # noqa
-    )
-    struct_org_phone = models.CharField(
-        blank=True,
-        max_length=255,
-        verbose_name=_('Telephone number'),
-        help_text=_('Include country code for best results. For example: +1-216-555-8000')
-    )
-    struct_org_address_street = models.CharField(
-        blank=True,
-        max_length=255,
-        verbose_name=_('Street address'),
-        help_text=_('House number and street. For example, 55 Public Square Suite 1710')
-    )
-    struct_org_address_locality = models.CharField(
-        blank=True,
-        max_length=255,
-        verbose_name=_('City'),
-        help_text=_('City or locality. For example, Cleveland')
-    )
-    struct_org_address_region = models.CharField(
-        blank=True,
-        max_length=255,
-        verbose_name=_('State'),
-        help_text=_('State, province, county, or region. For example, OH')
-    )
-    struct_org_address_postal = models.CharField(
-        blank=True,
-        max_length=255,
-        verbose_name=_('Postal code'),
-        help_text=_('Zip or postal code. For example, 44113')
-    )
-    struct_org_address_country = models.CharField(
-        blank=True,
-        max_length=255,
-        verbose_name=_('Country'),
-        help_text=_('For example, USA. Two-letter ISO 3166-1 alpha-2 country code is also acceptable https://en.wikipedia.org/wiki/ISO_3166-1'),  # noqa
-    )
-    struct_org_geo_lat = models.DecimalField(
-        blank=True,
-        null=True,
-        max_digits=10,
-        decimal_places=8,
-        verbose_name=_('Geographic latitude')
-    )
-    struct_org_geo_lng = models.DecimalField(
-        blank=True,
-        null=True,
-        max_digits=10,
-        decimal_places=8,
-        verbose_name=_('Geographic longitude')
-    )
-    struct_org_hours = StreamField(
-        [
-            ('hours', OpenHoursBlock()),
-        ],
-        blank=True,
-        verbose_name=_('Hours of operation')
-    )
-    struct_org_actions = StreamField(
-        [
-            ('actions', StructuredDataActionBlock())
-        ],
-        blank=True,
-        verbose_name=_('Actions')
-    )
-    struct_org_extra_json = models.TextField(
-        blank=True,
-        verbose_name=_('Additional Organization markup'),
-        help_text=_('Additional JSON-LD inserted into the Organization dictionary. Must be properties of https://schema.org/Organization or the selected organization type.'),  # noqa
-    )
+    seo_image_sources = [
+        "og_image",
+        "cover_image",
+    ]
 
 
     ###############
     ###############
     # Classify
     # Classify
@@ -417,45 +313,7 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
         )
         )
     ]
     ]
 
 
-    promote_panels = [
-        MultiFieldPanel(
-            [
-                FieldPanel('slug'),
-                FieldPanel('seo_title'),
-                FieldPanel('search_description'),
-                ImageChooserPanel('og_image'),
-            ],
-            _('Page Meta Data')
-        ),
-        MultiFieldPanel(
-            [
-                HelpPanel(
-                    heading=_('About Organization Structured Data'),
-                    content=_("""The fields below help define brand, contact, and storefront
-                    information to search engines. This information should be filled out on
-                    the site’s root page (Home Page). If your organization has multiple locations,
-                    then also fill this info out on each location page using that particular
-                    location’s info."""),
-                ),
-                FieldPanel('struct_org_type'),
-                FieldPanel('struct_org_name'),
-                ImageChooserPanel('struct_org_logo'),
-                ImageChooserPanel('struct_org_image'),
-                FieldPanel('struct_org_phone'),
-                FieldPanel('struct_org_address_street'),
-                FieldPanel('struct_org_address_locality'),
-                FieldPanel('struct_org_address_region'),
-                FieldPanel('struct_org_address_postal'),
-                FieldPanel('struct_org_address_country'),
-                FieldPanel('struct_org_geo_lat'),
-                FieldPanel('struct_org_geo_lng'),
-                StreamFieldPanel('struct_org_hours'),
-                StreamFieldPanel('struct_org_actions'),
-                FieldPanel('struct_org_extra_json'),
-            ],
-            _('Structured Data - Organization')
-        ),
-    ]
+    promote_panels = SeoMixin.seo_meta_panels + SeoMixin.seo_struct_panels
 
 
     settings_panels = Page.settings_panels + [
     settings_panels = Page.settings_panels + [
         StreamFieldPanel('content_walls'),
         StreamFieldPanel('content_walls'),
@@ -504,41 +362,50 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
 
 
         return TabbedInterface(panels).bind_to(model=cls)
         return TabbedInterface(panels).bind_to(model=cls)
 
 
-    def get_struct_org_name(self):
-        """
-        Gets org name for sturctured data using a fallback.
-        """
-        if self.struct_org_name:
-            return self.struct_org_name
-        return self.get_site().site_name
-
-    def get_struct_org_logo(self):
+    @property
+    def seo_logo(self) -> "Optional[AbstractImage]":
         """
         """
-        Gets logo for structured data using a fallback.
+        Override method in SeoMixin.
+        Gets the primary logo of the organization.
         """
         """
-        if self.struct_org_logo:
-            return self.struct_org_logo
+        logo = super().seo_logo
+        if logo:
+            return logo
         else:
         else:
             layout_settings = LayoutSettings.for_site(self.get_site())
             layout_settings = LayoutSettings.for_site(self.get_site())
             if layout_settings.logo:
             if layout_settings.logo:
                 return layout_settings.logo
                 return layout_settings.logo
         return None
         return None
 
 
+    @property
+    def seo_image(self) -> "Optional[AbstractImage]":
+        """
+        Override method in SeoMixin.
+        Fallback to logo if opengraph image is not specified.
+        """
+        img = super().seo_image
+        if img is None:
+            return self.seo_logo
+        return img
+
+    @property
+    def seo_twitter_card_content(self) -> str:
+        """
+        Override of method in SeoMixin.
+        Show a large twitter card if the page has an image set.
+        """
+        if self.seo_image:
+            return TwitterCard.LARGE.value
+        return self.seo_twitter_card.value
+
     def get_template(self, request, *args, **kwargs):
     def get_template(self, request, *args, **kwargs):
         """
         """
         Override parent to serve different templates based on querystring.
         Override parent to serve different templates based on querystring.
         """
         """
-        if 'amp' in request.GET and hasattr(self, 'amp_template'):
-            seo_settings = SeoSettings.for_request(request)
-            if seo_settings.amp_pages:
-                if request.is_ajax():
-                    return self.ajax_template or self.amp_template
-                return self.amp_template
-
         if self.custom_template:
         if self.custom_template:
             return self.custom_template
             return self.custom_template
 
 
-        return super(CoderedPage, self).get_template(request, args, kwargs)
+        return super().get_template(request, args, kwargs)
 
 
     def get_index_children(self):
     def get_index_children(self):
         """
         """
@@ -615,6 +482,7 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
         context['content_walls'] = self.get_content_walls(check_child_setting=False)
         context['content_walls'] = self.get_content_walls(check_child_setting=False)
         return context
         return context
 
 
+
 ###############################################################################
 ###############################################################################
 # Abstract pages providing pre-built common website functionality, suitable for subclassing.
 # Abstract pages providing pre-built common website functionality, suitable for subclassing.
 # These are abstract so subclasses can override fields if desired.
 # These are abstract so subclasses can override fields if desired.
@@ -681,7 +549,6 @@ class CoderedArticlePage(CoderedWebPage):
         abstract = True
         abstract = True
 
 
     template = 'coderedcms/pages/article_page.html'
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.html'
 
 
     # Override body to provide simpler content
     # Override body to provide simpler content
     body = StreamField(CONTENT_STREAMBLOCKS, null=True, blank=True)
     body = StreamField(CONTENT_STREAMBLOCKS, null=True, blank=True)
@@ -712,25 +579,57 @@ class CoderedArticlePage(CoderedWebPage):
     )
     )
 
 
     def get_author_name(self):
     def get_author_name(self):
+        warnings.warn(
+            ("CoderedArticlePage.get_author_name has been replaced with "
+             "CoderedArticlePage.seo_author"),
+            DeprecationWarning,
+        )
+        return self.seo_author
+
+    @property
+    def seo_author(self) -> str:
         """
         """
+        Override of method in SeoMixin.
         Gets author name using a fallback.
         Gets author name using a fallback.
         """
         """
         if self.author_display:
         if self.author_display:
             return self.author_display
             return self.author_display
         if self.author:
         if self.author:
             return self.author.get_full_name()
             return self.author.get_full_name()
-        return ''
+        if self.owner:
+            return self.owner.get_full_name()
+        return ""
 
 
     def get_pub_date(self):
     def get_pub_date(self):
+        warnings.warn(
+            ("CoderedArticlePage.get_pub_date has been replaced with "
+             "CoderedArticlePage.seo_published_at"),
+            DeprecationWarning,
+        )
+        return self.seo_published_at
+
+    @property
+    def seo_published_at(self) -> datetime:
         """
         """
+        Override of method in SeoMixin.
         Gets published date.
         Gets published date.
         """
         """
         if self.date_display:
         if self.date_display:
             return self.date_display
             return self.date_display
-        return ''
+        return self.first_published_at
 
 
     def get_description(self):
     def get_description(self):
+        warnings.warn(
+            ("CoderedArticlePage.get_description has been replaced with "
+             "CoderedArticlePage.seo_description"),
+            DeprecationWarning,
+        )
+        return self.seo_description
+
+    @property
+    def seo_description(self) -> str:
         """
         """
+        Override of method in SeoMixin.
         Gets the description using a fallback.
         Gets the description using a fallback.
         """
         """
         if self.search_description:
         if self.search_description:
@@ -739,7 +638,7 @@ class CoderedArticlePage(CoderedWebPage):
             return self.caption
             return self.caption
         if self.body_preview:
         if self.body_preview:
             return self.body_preview
             return self.body_preview
-        return ''
+        return ""
 
 
     search_fields = (
     search_fields = (
         CoderedWebPage.search_fields +
         CoderedWebPage.search_fields +
@@ -869,6 +768,43 @@ class CoderedEventPage(CoderedWebPage, BaseEvent):
             if occurrences:
             if occurrences:
                 return sorted(occurrences, key=lambda tup: tup[0])[0]
                 return sorted(occurrences, key=lambda tup: tup[0])[0]
 
 
+    @property
+    def seo_struct_event_dict(self) -> dict:
+        next_occ = self.most_recent_occurrence
+        sd_dict = {
+            "@context": "https://schema.org/",
+            "@type": "Event",
+            "name": self.title,
+            "description": self.seo_description,
+            "startDate": next_occ.start,
+            "endDate": next_occ.end,
+            "mainEntityOfPage": {
+                "@type": "WebPage",
+                "@id": self.get_full_url,
+            },
+        }
+
+        if self.seo_image:
+            sd_dict.update({"image": get_struct_data_images(self.seo_image)})
+
+        if self.address:
+            sd_dict.update({
+                "location": {
+                    "@type": "Place",
+                    "name": self.title,
+                    "address": {
+                        "@type": "PostalAddress",
+                        "streetAddress": self.address,
+                    },
+                },
+            })
+
+        return sd_dict
+
+    @property
+    def seo_struct_event_json(self) -> str:
+        return json.dumps(self.seo_struct_event_dict, cls=StructDataEncoder)
+
     def query_occurrences(self, num_of_instances_to_return=None, **kwargs):
     def query_occurrences(self, num_of_instances_to_return=None, **kwargs):
         """
         """
         Returns a list of all upcoming event instances for the specified query.
         Returns a list of all upcoming event instances for the specified query.

+ 0 - 13
coderedcms/models/tests/test_page_models.py

@@ -1,6 +1,5 @@
 from django.test import Client
 from django.test import Client
 from wagtail.tests.utils import WagtailPageTests
 from wagtail.tests.utils import WagtailPageTests
-from wagtail.core.models import Site
 
 
 from coderedcms.models.page_models import (
 from coderedcms.models.page_models import (
     CoderedArticleIndexPage,
     CoderedArticleIndexPage,
@@ -26,9 +25,6 @@ from coderedcms.tests.testapp.models import (
     StreamFormPage,
     StreamFormPage,
     WebPage
     WebPage
 )
 )
-from coderedcms.models.wagtailsettings_models import (
-    SeoSettings
-)
 
 
 
 
 class BasicPageTestCase():
 class BasicPageTestCase():
@@ -165,15 +161,6 @@ class CoderedStreamFormPageTestCase(AbstractPageTestCase, WagtailPageTests):
 class ArticlePageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
 class ArticlePageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
     model = ArticlePage
     model = ArticlePage
 
 
-    def test_amp(self):
-        site = Site.objects.filter(is_default_site=True)[0]
-        settings = SeoSettings.for_site(site)
-        settings.amp_pages = True
-        settings.save()
-
-        response = self.client.get(self.basic_page.url + '?amp')
-        self.assertEqual(response.status_code, 200)
-
 
 
 class ArticleIndexPageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
 class ArticleIndexPageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
     model = ArticleIndexPage
     model = ArticleIndexPage

+ 1 - 45
coderedcms/models/wagtailsettings_models.py

@@ -7,7 +7,7 @@ Global project or developer settings should be defined in coderedcms.settings.py
 import json
 import json
 from django.db import models
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 from django.utils.translation import gettext_lazy as _
-from wagtail.admin.edit_handlers import HelpPanel, FieldPanel, MultiFieldPanel
+from wagtail.admin.edit_handlers import FieldPanel, MultiFieldPanel
 from wagtail.images.edit_handlers import ImageChooserPanel
 from wagtail.images.edit_handlers import ImageChooserPanel
 from wagtail.contrib.settings.models import BaseSetting, register_setting
 from wagtail.contrib.settings.models import BaseSetting, register_setting
 from wagtail.images import get_image_model_string
 from wagtail.images import get_image_model_string
@@ -330,50 +330,6 @@ class GeneralSettings(BaseSetting):
         verbose_name = _('General')
         verbose_name = _('General')
 
 
 
 
-@register_setting(icon='fa-line-chart')
-class SeoSettings(BaseSetting):
-    """
-    Additional search engine optimization and meta tags
-    that can be turned on or off.
-    """
-    class Meta:
-        verbose_name = _('SEO')
-
-    og_meta = models.BooleanField(
-        default=True,
-        verbose_name=_('Use OpenGraph Markup'),
-        help_text=_('Show an optimized preview when linking to this site on Facebook, Linkedin, Twitter, and others. See http://ogp.me/.'),  # noqa
-    )
-    twitter_meta = models.BooleanField(
-        default=True,
-        verbose_name=_('Use Twitter Markup'),
-        help_text=_('Shows content as a "card" when linking to this site on Twitter. See https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards.'),  # noqa
-    )
-    struct_meta = models.BooleanField(
-        default=True,
-        verbose_name=_('Use Structured Data'),
-        help_text=_('Optimizes information about your organization for search engines. See https://schema.org/.'),  # noqa
-    )
-    amp_pages = models.BooleanField(
-        default=True,
-        verbose_name=_('Use AMP Pages'),
-        help_text=_('Generates an alternate AMP version of Article pages that are preferred by search engines. See https://www.ampproject.org/'),  # noqa
-    )
-
-    panels = [
-        MultiFieldPanel(
-            [
-                FieldPanel('og_meta'),
-                FieldPanel('twitter_meta'),
-                FieldPanel('struct_meta'),
-                FieldPanel('amp_pages'),
-                HelpPanel(content=_('If these settings are enabled, the corresponding values in each page’s SEO tab are used.')),  # noqa
-            ],
-            heading=_('Search Engine Optimization')
-        )
-    ]
-
-
 @register_setting(icon='fa-puzzle-piece')
 @register_setting(icon='fa-puzzle-piece')
 class GoogleApiSettings(BaseSetting):
 class GoogleApiSettings(BaseSetting):
     """
     """

+ 1 - 0
coderedcms/project_template/basic/project_name/settings/base.py

@@ -36,6 +36,7 @@ INSTALLED_APPS = [
     'wagtailfontawesome',
     'wagtailfontawesome',
     'wagtailcache',
     'wagtailcache',
     'wagtailimportexport',
     'wagtailimportexport',
+    'wagtailseo',
 
 
     # Wagtail
     # Wagtail
     'wagtail.contrib.forms',
     'wagtail.contrib.forms',

+ 0 - 1
coderedcms/project_template/basic/website/models.py

@@ -24,7 +24,6 @@ class ArticlePage(CoderedArticlePage):
     parent_page_types = ['website.ArticleIndexPage']
     parent_page_types = ['website.ArticleIndexPage']
 
 
     template = 'coderedcms/pages/article_page.html'
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.html'
     search_template = 'coderedcms/pages/article_page.search.html'
     search_template = 'coderedcms/pages/article_page.search.html'
 
 
 
 

+ 1 - 0
coderedcms/project_template/sass/project_name/settings/base.py

@@ -35,6 +35,7 @@ INSTALLED_APPS = [
     'wagtailfontawesome',
     'wagtailfontawesome',
     'wagtailcache',
     'wagtailcache',
     'wagtailimportexport',
     'wagtailimportexport',
+    'wagtailseo',
 
 
     # Wagtail
     # Wagtail
     'wagtail.contrib.forms',
     'wagtail.contrib.forms',

+ 0 - 1
coderedcms/project_template/sass/website/models.py

@@ -24,7 +24,6 @@ class ArticlePage(CoderedArticlePage):
     parent_page_types = ['website.ArticleIndexPage']
     parent_page_types = ['website.ArticleIndexPage']
 
 
     template = 'coderedcms/pages/article_page.html'
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.html'
     search_template = 'coderedcms/pages/article_page.search.html'
     search_template = 'coderedcms/pages/article_page.search.html'
 
 
 
 

+ 0 - 165
coderedcms/schema.py

@@ -1,165 +0,0 @@
-SCHEMA_ORG_CHOICES = (
-    ('Organization', 'Organization'),
-    ('Airline', 'Organization > Airline'),
-    ('Corporation', 'Organization > Corporation'),
-    ('EducationalOrganization', 'Organization > EducationalOrganization'),
-    ('CollegeOrUniversity', 'Organization > EducationalOrganization > CollegeOrUniversity'),
-    ('ElementarySchool', 'Organization > EducationalOrganization > ElementarySchool'),
-    ('HighSchool', 'Organization > EducationalOrganization > HighSchool'),
-    ('MiddleSchool', 'Organization > EducationalOrganization > MiddleSchool'),
-    ('Preschool', 'Organization > EducationalOrganization > Preschool'),
-    ('School', 'Organization > EducationalOrganization > School'),
-    ('GovernmentOrganization', 'Organization > GovernmentOrganization'),
-    ('LocalBusiness', 'Organization > LocalBusiness'),
-    ('AnimalShelter', 'Organization > LocalBusiness > AnimalShelter'),
-    ('AutomotiveBusiness', 'Organization > LocalBusiness > AutomotiveBusiness'),
-    ('AutoBodyShop', 'Organization > LocalBusiness > AutomotiveBusiness > AutoBodyShop'),
-    ('AutoDealer', 'Organization > LocalBusiness > AutomotiveBusiness > AutoDealer'),
-    ('AutoPartsStore', 'Organization > LocalBusiness > AutomotiveBusiness > AutoPartsStore'),
-    ('AutoRental', 'Organization > LocalBusiness > AutomotiveBusiness > AutoRental'),
-    ('AutoRepair', 'Organization > LocalBusiness > AutomotiveBusiness > AutoRepair'),
-    ('AutoWash', 'Organization > LocalBusiness > AutomotiveBusiness > AutoWash'),
-    ('GasStation', 'Organization > LocalBusiness > AutomotiveBusiness > GasStation'),
-    ('MotorcycleDealer', 'Organization > LocalBusiness > AutomotiveBusiness > MotorcycleDealer'),
-    ('MotorcycleRepair', 'Organization > LocalBusiness > AutomotiveBusiness > MotorcycleRepair'),
-    ('ChildCare', 'Organization > LocalBusiness > ChildCare'),
-    ('Dentist', 'Organization > LocalBusiness > Dentist'),
-    ('DryCleaningOrLaundry', 'Organization > LocalBusiness > DryCleaningOrLaundry'),
-    ('EmergencyService', 'Organization > LocalBusiness > EmergencyService'),
-    ('FireStation', 'Organization > LocalBusiness > EmergencyService > FireStation'),
-    ('Hospital', 'Organization > LocalBusiness > EmergencyService > Hospital'),
-    ('PoliceStation', 'Organization > LocalBusiness > EmergencyService > PoliceStation'),
-    ('EmploymentAgency', 'Organization > LocalBusiness > EmploymentAgency'),
-    ('EntertainmentBusiness', 'Organization > LocalBusiness > EntertainmentBusiness'),
-    ('AdultEntertainment', 'Organization > LocalBusiness > EntertainmentBusiness > AdultEntertainment'),
-    ('AmusementPark', 'Organization > LocalBusiness > EntertainmentBusiness > AmusementPark'),
-    ('ArtGallery', 'Organization > LocalBusiness > EntertainmentBusiness > ArtGallery'),
-    ('Casino', 'Organization > LocalBusiness > EntertainmentBusiness > Casino'),
-    ('ComedyClub', 'Organization > LocalBusiness > EntertainmentBusiness > ComedyClub'),
-    ('MovieTheater', 'Organization > LocalBusiness > EntertainmentBusiness > MovieTheater'),
-    ('NightClub', 'Organization > LocalBusiness > EntertainmentBusiness > NightClub'),
-    ('FinancialService', 'Organization > LocalBusiness > FinancialService'),
-    ('AccountingService', 'Organization > LocalBusiness > FinancialService > AccountingService'),
-    ('AutomatedTeller', 'Organization > LocalBusiness > FinancialService > AutomatedTeller'),
-    ('BankOrCreditUnion', 'Organization > LocalBusiness > FinancialService > BankOrCreditUnion'),
-    ('InsuranceAgency', 'Organization > LocalBusiness > FinancialService > InsuranceAgency'),
-    ('FoodEstablishment', 'Organization > LocalBusiness > FoodEstablishment'),
-    ('Bakery', 'Organization > LocalBusiness > FoodEstablishment > Bakery'),
-    ('BarOrPub', 'Organization > LocalBusiness > FoodEstablishment > BarOrPub'),
-    ('Brewery', 'Organization > LocalBusiness > FoodEstablishment > Brewery'),
-    ('CafeOrCoffeeShop', 'Organization > LocalBusiness > FoodEstablishment > CafeOrCoffeeShop'),
-    ('FastFoodRestaurant', 'Organization > LocalBusiness > FoodEstablishment > FastFoodRestaurant'),
-    ('IceCreamShop', 'Organization > LocalBusiness > FoodEstablishment > IceCreamShop'),
-    ('Restaurant', 'Organization > LocalBusiness > FoodEstablishment > Restaurant'),
-    ('Winery', 'Organization > LocalBusiness > FoodEstablishment > Winery'),
-    ('GovernmentOffice', 'Organization > LocalBusiness > GovernmentOffice'),
-    ('PostOffice', 'Organization > LocalBusiness > GovernmentOffice > PostOffice'),
-    ('HealthAndBeautyBusiness', 'Organization > LocalBusiness > HealthAndBeautyBusiness'),
-    ('BeautySalon', 'Organization > LocalBusiness > HealthAndBeautyBusiness > BeautySalon'),
-    ('DaySpa', 'Organization > LocalBusiness > HealthAndBeautyBusiness > DaySpa'),
-    ('HairSalon', 'Organization > LocalBusiness > HealthAndBeautyBusiness > HairSalon'),
-    ('HealthClub', 'Organization > LocalBusiness > HealthAndBeautyBusiness > HealthClub'),
-    ('NailSalon', 'Organization > LocalBusiness > HealthAndBeautyBusiness > NailSalon'),
-    ('TattooParlor', 'Organization > LocalBusiness > HealthAndBeautyBusiness > TattooParlor'),
-    ('HomeAndConstructionBusiness', 'Organization > LocalBusiness > HomeAndConstructionBusiness'),
-    ('Electrician', 'Organization > LocalBusiness > HomeAndConstructionBusiness > Electrician'),
-    ('GeneralContractor', 'Organization > LocalBusiness > HomeAndConstructionBusiness > GeneralContractor'),
-    ('HVACBusiness', 'Organization > LocalBusiness > HomeAndConstructionBusiness > HVACBusiness'),
-    ('HousePainter', 'Organization > LocalBusiness > HomeAndConstructionBusiness > HousePainter'),
-    ('Locksmith', 'Organization > LocalBusiness > HomeAndConstructionBusiness > Locksmith'),
-    ('MovingCompany', 'Organization > LocalBusiness > HomeAndConstructionBusiness > MovingCompany'),
-    ('Plumber', 'Organization > LocalBusiness > HomeAndConstructionBusiness > Plumber'),
-    ('RoofingContractor', 'Organization > LocalBusiness > HomeAndConstructionBusiness > RoofingContractor'),
-    ('InternetCafe', 'Organization > LocalBusiness > InternetCafe'),
-    ('LegalService', 'Organization > LocalBusiness > LegalService'),
-    ('Attorney', 'Organization > LocalBusiness > LegalService > Attorney'),
-    ('Notary', 'Organization > LocalBusiness > LegalService > Notary'),
-    ('Library', 'Organization > LocalBusiness > Library'),
-    ('LodgingBusiness', 'Organization > LocalBusiness > LodgingBusiness'),
-    ('BedAndBreakfast', 'Organization > LocalBusiness > LodgingBusiness > BedAndBreakfast'),
-    ('Campground', 'Organization > LocalBusiness > LodgingBusiness > Campground'),
-    ('Hostel', 'Organization > LocalBusiness > LodgingBusiness > Hostel'),
-    ('Hotel', 'Organization > LocalBusiness > LodgingBusiness > Hotel'),
-    ('Motel', 'Organization > LocalBusiness > LodgingBusiness > Motel'),
-    ('Resort', 'Organization > LocalBusiness > LodgingBusiness > Resort'),
-    ('ProfessionalService', 'Organization > LocalBusiness > ProfessionalService'),
-    ('RadioStation', 'Organization > LocalBusiness > RadioStation'),
-    ('RealEstateAgent', 'Organization > LocalBusiness > RealEstateAgent'),
-    ('RecyclingCenter', 'Organization > LocalBusiness > RecyclingCenter'),
-    ('SelfStorage', 'Organization > LocalBusiness > SelfStorage'),
-    ('ShoppingCenter', 'Organization > LocalBusiness > ShoppingCenter'),
-    ('SportsActivityLocation', 'Organization > LocalBusiness > SportsActivityLocation'),
-    ('BowlingAlley', 'Organization > LocalBusiness > SportsActivityLocation > BowlingAlley'),
-    ('ExerciseGym', 'Organization > LocalBusiness > SportsActivityLocation > ExerciseGym'),
-    ('GolfCourse', 'Organization > LocalBusiness > SportsActivityLocation > GolfCourse'),
-    ('HealthClub', 'Organization > LocalBusiness > SportsActivityLocation > HealthClub'),
-    ('PublicSwimmingPool', 'Organization > LocalBusiness > SportsActivityLocation > PublicSwimmingPool'),
-    ('SkiResort', 'Organization > LocalBusiness > SportsActivityLocation > SkiResort'),
-    ('SportsClub', 'Organization > LocalBusiness > SportsActivityLocation > SportsClub'),
-    ('StadiumOrArena', 'Organization > LocalBusiness > SportsActivityLocation > StadiumOrArena'),
-    ('TennisComplex', 'Organization > LocalBusiness > SportsActivityLocation > TennisComplex'),
-    ('Store', 'Organization > LocalBusiness > Store'),
-    ('AutoPartsStore', 'Organization > LocalBusiness > Store > AutoPartsStore'),
-    ('BikeStore', 'Organization > LocalBusiness > Store > BikeStore'),
-    ('BookStore', 'Organization > LocalBusiness > Store > BookStore'),
-    ('ClothingStore', 'Organization > LocalBusiness > Store > ClothingStore'),
-    ('ComputerStore', 'Organization > LocalBusiness > Store > ComputerStore'),
-    ('ConvenienceStore', 'Organization > LocalBusiness > Store > ConvenienceStore'),
-    ('DepartmentStore', 'Organization > LocalBusiness > Store > DepartmentStore'),
-    ('ElectronicsStore', 'Organization > LocalBusiness > Store > ElectronicsStore'),
-    ('Florist', 'Organization > LocalBusiness > Store > Florist'),
-    ('FurnitureStore', 'Organization > LocalBusiness > Store > FurnitureStore'),
-    ('GardenStore', 'Organization > LocalBusiness > Store > GardenStore'),
-    ('GroceryStore', 'Organization > LocalBusiness > Store > GroceryStore'),
-    ('HardwareStore', 'Organization > LocalBusiness > Store > HardwareStore'),
-    ('HobbyShop', 'Organization > LocalBusiness > Store > HobbyShop'),
-    ('HomeGoodsStore', 'Organization > LocalBusiness > Store > HomeGoodsStore'),
-    ('JewelryStore', 'Organization > LocalBusiness > Store > JewelryStore'),
-    ('LiquorStore', 'Organization > LocalBusiness > Store > LiquorStore'),
-    ('MensClothingStore', 'Organization > LocalBusiness > Store > MensClothingStore'),
-    ('MobilePhoneStore', 'Organization > LocalBusiness > Store > MobilePhoneStore'),
-    ('MovieRentalStore', 'Organization > LocalBusiness > Store > MovieRentalStore'),
-    ('MusicStore', 'Organization > LocalBusiness > Store > MusicStore'),
-    ('OfficeEquipmentStore', 'Organization > LocalBusiness > Store > OfficeEquipmentStore'),
-    ('OutletStore', 'Organization > LocalBusiness > Store > OutletStore'),
-    ('PawnShop', 'Organization > LocalBusiness > Store > PawnShop'),
-    ('PetStore', 'Organization > LocalBusiness > Store > PetStore'),
-    ('ShoeStore', 'Organization > LocalBusiness > Store > ShoeStore'),
-    ('SportingGoodsStore', 'Organization > LocalBusiness > Store > SportingGoodsStore'),
-    ('TireShop', 'Organization > LocalBusiness > Store > TireShop'),
-    ('ToyStore', 'Organization > LocalBusiness > Store > ToyStore'),
-    ('WholesaleStore', 'Organization > LocalBusiness > Store > WholesaleStore'),
-    ('TelevisionStation', 'Organization > LocalBusiness > TelevisionStation'),
-    ('TouristInformationCenter', 'Organization > LocalBusiness > TouristInformationCenter'),
-    ('TravelAgency', 'Organization > LocalBusiness > TravelAgency'),
-    ('MedicalOrganization', 'Organization > MedicalOrganization'),
-    ('Dentist', 'Organization > MedicalOrganization > Dentist'),
-    ('Hospital', 'Organization > MedicalOrganization > Hospital'),
-    ('Pharmacy', 'Organization > MedicalOrganization > Pharmacy'),
-    ('Physician', 'Organization > MedicalOrganization > Physician'),
-    ('NGO', 'Organization > NGO'),
-    ('PerformingGroup', 'Organization > PerformingGroup'),
-    ('DanceGroup', 'Organization > PerformingGroup > DanceGroup'),
-    ('MusicGroup', 'Organization > PerformingGroup > MusicGroup'),
-    ('TheaterGroup', 'Organization > PerformingGroup > TheaterGroup'),
-    ('SportsOrganization', 'Organization > SportsOrganization'),
-    ('SportsTeam', 'Organization > SportsOrganization > SportsTeam'),
-)
-
-SCHEMA_ACTION_CHOICES = (
-    ('OrderAction', 'OrderAction'),
-    ('ReserveAction', 'ReserveAction'),
-)
-
-SCHEMA_RESULT_CHOICES = (
-    ('Reservation', 'Reservation'),
-    ('BusReservation', 'BusReservation'),
-    ('EventReservation', 'EventReservation'),
-    ('FlightReservation', 'FlightReservation'),
-    ('FoodEstablishmentReservation', 'FoodEstablishmentReservation'),
-    ('LodgingReservation', 'LodgingReservation'),
-    ('RentalCarReservation', 'RentalCarReservation'),
-    ('ReservationPackage', 'ReservationPackage'),
-    ('TaxiReservation', 'TaxiReservation'),
-    ('TrainReservation', 'TrainReservation'),
-)

+ 2 - 2
coderedcms/templates/coderedcms/blocks/article_block_card.html

@@ -7,11 +7,11 @@
     {% endif %}
     {% endif %}
     <div class="card-body">
     <div class="card-body">
         <h5 class="card-title">{{article.title}}</h5>
         <h5 class="card-title">{{article.title}}</h5>
-        <div class="card-subtitle mb-2 text-muted">{{article.get_pub_date}} {% if article.get_pub_date and article.get_author_name %} &bull; {% endif %} {{article.get_author_name}}</div>
+        <div class="card-subtitle mb-2 text-muted">{{article.seo_published_at}} &bull; {{article.seo_author}}</div>
         <div class="card-subtitle mb-2 text-muted">{{article.caption}}</div>
         <div class="card-subtitle mb-2 text-muted">{{article.caption}}</div>
         {% if self.show_preview %}
         {% if self.show_preview %}
         <p class="card-text">{{article.body_preview}}</p>
         <p class="card-text">{{article.body_preview}}</p>
         {% endif %}
         {% endif %}
         <a href="{{article.url}}" title="{{article.title}}">Read more &raquo;</a>
         <a href="{{article.url}}" title="{{article.title}}">Read more &raquo;</a>
     </div>
     </div>
-</div>
+</div>

+ 2 - 9
coderedcms/templates/coderedcms/blocks/embed_video_block.html

@@ -1,12 +1,5 @@
 {% extends "coderedcms/blocks/base_block.html" %}
 {% extends "coderedcms/blocks/base_block.html" %}
-{% load coderedcms_tags %}
 
 
 {% block block_render %}
 {% block block_render %}
-
-{% if format == 'amp' %}
-    {{ self.url.html|convert_to_amp }}
-{% else %}
-    {{ self.url }}
-{% endif %}
-
-{% endblock %}
+{{ self.url }}
+{% endblock %}

+ 2 - 2
coderedcms/templates/coderedcms/blocks/pagelist_article_media.html

@@ -12,8 +12,8 @@
         {% endif %}
         {% endif %}
         <div class="media-body">
         <div class="media-body">
             <h5 class="mt-0"><a href="{{article.url}}">{{article.title}}</a></h5>
             <h5 class="mt-0"><a href="{{article.url}}">{{article.title}}</a></h5>
-            <div class="mb-2 text-muted">{{article.get_pub_date}} {% if article.get_pub_date and article.get_author_name %} &bull; {% endif %} {{article.get_author_name}}</div>
-            <div class="mb-2 text-muted">{{article.caption}}</div>
+            <div class="card-subtitle mb-2 text-muted">{{article.seo_published_at}} &bull; {{article.seo_author}}</div>
+            <div class="card-subtitle mb-2 text-muted">{{article.caption}}</div>
             {% if self.show_preview %}
             {% if self.show_preview %}
             <p>{{article.body_preview}}</p>
             <p>{{article.body_preview}}</p>
             {% endif %}
             {% endif %}

+ 1 - 7
coderedcms/templates/coderedcms/blocks/rich_text_block.html

@@ -1,11 +1,5 @@
-{% load wagtailcore_tags coderedcms_tags %}
+{% load wagtailcore_tags %}
 
 
 {% block block_render %}
 {% block block_render %}
-
-{% if format == 'amp' %}
-{{ self|richtext|convert_to_amp }}
-{% else %}
 {{ self|richtext }}
 {{ self|richtext }}
-{% endif %}
-
 {% endblock %}
 {% endblock %}

+ 0 - 22
coderedcms/templates/coderedcms/blocks/struct_data_action.json

@@ -1,22 +0,0 @@
-{
-    "target": {
-      "@type": "EntryPoint",
-      "urlTemplate": "{{self.target}}",
-      "inLanguage": "{{self.language}}",
-      "actionPlatform": [
-        "http://schema.org/DesktopWebPlatform",
-        "http://schema.org/IOSPlatform",
-        "http://schema.org/AndroidPlatform"
-      ]
-    },
-    {% if self.result_type %}
-    "result": {
-      "@type": "{{self.result_type}}",
-      "name": "{{self.result_name}}"
-    },
-    {% endif %}
-    {% if self.extra_json %}
-    {{self.extra_json}},
-    {% endif %}
-    "@type": "{{self.action_type}}"
-}

+ 0 - 6
coderedcms/templates/coderedcms/blocks/struct_data_hours.json

@@ -1,6 +0,0 @@
-{
-    "@type": "OpeningHoursSpecification",
-    "dayOfWeek": {{self.days_json|safe}},
-    "opens": "{{self.start_time|date:'H:i'}}",
-    "closes": "{{self.end_time|date:'H:i'}}"
-}

+ 0 - 50
coderedcms/templates/coderedcms/includes/struct_data_article.json

@@ -1,50 +0,0 @@
-{% load wagtailimages_tags %}
-
-{
-  "@context": "http://schema.org",
-  "mainEntityOfPage": {
-    "@type": "WebPage",
-    "@id": "{{page.get_full_url}}"
-  },
-  "headline": "{{page.title}}",
-  "description": "{{page.get_description}}",
-
-  {# Get different aspect ratios. Use huge numbers because wagtail will not upscale, #}
-  {# but will max out at the image's original resolution using the specified aspect ratio. #}
-  {# Google wants them high resolution. #}
-  {% if page.og_image %}
-    {% image page.struct_org_image fill-10000x10000 as img_11 %}
-    {% image page.struct_org_image fill-40000x30000 as img_21 %}
-    {% image page.struct_org_image fill-16000x9000 as img_169 %}
-    "image": [
-        "{{self.get_site.root_url}}{{img_11.url}}",
-        "{{self.get_site.root_url}}{{img_21.url}}",
-        "{{self.get_site.root_url}}{{img_169.url}}"
-    ],
-  {% elif page.cover_image %}
-    {% image page.cover_image fill-10000x10000 as img_11 %}
-    {% image page.cover_image fill-40000x30000 as img_21 %}
-    {% image page.cover_image fill-16000x9000 as img_169 %}
-    "image": [
-        "{{self.get_site.root_url}}{{img_11.url}}",
-        "{{self.get_site.root_url}}{{img_21.url}}",
-        "{{self.get_site.root_url}}{{img_169.url}}"
-    ],
-  {% endif %}
-
-  "datePublished": "{{page.get_pub_date|date:'c'}}",
-  "dateModified": "{{page.last_published_at|date:'c'}}",
-
-  "author": {
-    "@type": "Person",
-    "name": "{{page.get_author_name}}"
-  },
-
-  {% if page.struct_org_type %}
-  "publisher": {% include "coderedcms/includes/struct_data_org.json" with page=page org_mode=True %},
-  {% elif page.get_site.root_page.specific.struct_org_type %}
-  "publisher": {% include "coderedcms/includes/struct_data_org.json" with page=page.get_site.root_page.specific org_mode=True %},
-  {% endif %}
-
-  "@type": "Article"
-}

+ 0 - 58
coderedcms/templates/coderedcms/includes/struct_data_event.json

@@ -1,58 +0,0 @@
-{% load wagtailimages_tags coderedcms_tags %}
-
-{
-  "@context": "http://schema.org",
-  "mainEntityOfPage": {
-    "@type": "WebPage",
-    "@id": "{{page.get_full_url}}"
-  },
-
-  {% if page.search_description %}
-  "description": "{{page.search_description}}",
-  {% endif %}
-
-  {# Get different aspect ratios. Use huge numbers because wagtail will not upscale, #}
-  {# but will max out at the image's original resolution using the specified aspect ratio. #}
-  {# Google wants them high resolution. #}
-  {% if page.og_image %}
-    {% image page.struct_org_image fill-10000x10000 as img_11 %}
-    {% image page.struct_org_image fill-40000x30000 as img_21 %}
-    {% image page.struct_org_image fill-16000x9000 as img_169 %}
-    "image": [
-        "{{self.get_site.root_url}}{{img_11.url}}",
-        "{{self.get_site.root_url}}{{img_21.url}}",
-        "{{self.get_site.root_url}}{{img_169.url}}"
-    ],
-  {% elif page.cover_image %}
-    {% image page.cover_image fill-10000x10000 as img_11 %}
-    {% image page.cover_image fill-40000x30000 as img_21 %}
-    {% image page.cover_image fill-16000x9000 as img_169 %}
-    "image": [
-        "{{self.get_site.root_url}}{{img_11.url}}",
-        "{{self.get_site.root_url}}{{img_21.url}}",
-        "{{self.get_site.root_url}}{{img_169.url}}"
-    ],
-  {% endif %}
-
-  {% if self.address %}
-  "location":{
-    "@type": "Place",
-    "name": "{{self.title}}",
-    "address":{
-      "@type": "PostalAddress",
-      "streetAddress": "{{self.address}}"
-    }
-  },
-  {% endif %}
-
-  "name": "{{self.title}}",
-
-  {% with self.most_recent_occurrence as nextup %}
-  "startDate": "{{nextup.0|structured_data_datetime}}",
-  {% if nextup.1 %}
-  "endDate": "{{nextup.1|structured_data_datetime}}",
-  {% endif %}
-  {% endwith %}
-
-  "@type": "Event"
-}

+ 0 - 80
coderedcms/templates/coderedcms/includes/struct_data_org.json

@@ -1,80 +0,0 @@
-{% load wagtailimages_tags %}
-
-{# Passing org_mode=True will render this as an "Organization" type #}
-{# which is required for some other objects such as publisher of an article. #}
-
-{
-    "@context": "http://schema.org",
-    "@type": "{% if org_mode %}Organization{% else %}{{page.struct_org_type}}{% endif %}",
-    "url": "{{page.get_full_url}}",
-    "name": "{{page.get_struct_org_name}}",
-
-    {% if page.get_struct_org_logo %}
-        {% image page.get_struct_org_logo original as logo_img %}
-        "logo": {
-            "@type": "ImageObject",
-            "url": "{{page.get_site.root_url}}{{logo_img.url}}"
-        },
-    {% endif %}
-
-    {% if page.struct_org_image %}
-        {# Get different aspect ratios. Use huge numbers because wagtail will not upscale, #}
-        {# but will max out at the image's original resolution using the specified aspect ratio. #}
-        {# Google wants them high resolution. #}
-        {% image page.struct_org_image fill-10000x10000 as img_11 %}
-        {% image page.struct_org_image fill-40000x30000 as img_21 %}
-        {% image page.struct_org_image fill-16000x9000 as img_169 %}
-        "image": [
-            "{{self.get_site.root_url}}{{img_11.url}}",
-            "{{self.get_site.root_url}}{{img_21.url}}",
-            "{{self.get_site.root_url}}{{img_169.url}}"
-        ],
-    {% endif %}
-
-    {% if page.struct_org_phone %}
-        "telephone": "{{page.struct_org_phone}}",
-    {% endif %}
-
-    {% if page.struct_org_address_street %}
-        "address": {
-            "@type": "PostalAddress",
-            "streetAddress": "{{page.struct_org_address_street}}",
-            "addressLocality": "{{page.struct_org_address_locality}}",
-            "addressRegion": "{{page.struct_org_address_region}}",
-            "postalCode": "{{page.struct_org_address_postal}}",
-            "addressCountry": "{{page.struct_org_address_country}}"
-        },
-    {% endif %}
-
-    {% if page.struct_org_geo_lat and page.struct_org_geo_lng and not org_mode %}
-        "geo": {
-            "@type": "GeoCoordinates",
-            "latitude": {{page.struct_org_geo_lat}},
-            "longitude": {{page.struct_org_geo_lng}}
-        },
-    {% endif %}
-
-    {% if page.struct_org_hours and not org_mode %}
-        "openingHoursSpecification": [
-        {% for spec in page.struct_org_hours %}
-            {{spec}}
-            {% if not forloop.last %},{% endif %}
-        {% endfor %}
-        ],
-    {% endif %}
-
-    {% if page.struct_org_actions and not org_mode %}
-        "potentialAction": [
-        {% for action in page.struct_org_actions %}
-            {{action}}
-            {% if not forloop.last %},{% endif %}
-        {% endfor %}
-        ],
-    {% endif %}
-
-    {% if page.struct_org_extra_json %}
-        {{page.struct_org_extra_json|safe}},
-    {% endif %}
-
-    "sameAs": {{settings.coderedcms.SocialMediaSettings.social_json|safe}}
-}

+ 1 - 1
coderedcms/templates/coderedcms/pages/article_index_page.html

@@ -29,7 +29,7 @@
             <div class="col-md">
             <div class="col-md">
                 <h3><a href="{% pageurl article %}">{{article.title}}</a></h3>
                 <h3><a href="{% pageurl article %}">{{article.title}}</a></h3>
                 {% if self.show_captions and article.specific.caption %}<p class="lead">{{article.specific.caption}}</p>{% endif %}
                 {% if self.show_captions and article.specific.caption %}<p class="lead">{{article.specific.caption}}</p>{% endif %}
-                {% if self.show_meta %}<p>{{article.specific.get_pub_date}}{% if article.specific.get_author_name %} &bull; {{article.specific.get_author_name}}{% endif %}</p>{% endif %}
+                {% if self.show_meta %}<p>{{article.specific.seo_published_at}} &bull; {{article.specific.seo_author}}</p>{% endif %}
                 {% if self.show_preview_text %}<p>{{article.specific.body_preview}}</p>{% endif %}
                 {% if self.show_preview_text %}<p>{{article.specific.body_preview}}</p>{% endif %}
             </div>
             </div>
         </div>
         </div>

+ 0 - 56
coderedcms/templates/coderedcms/pages/article_page.amp.html

@@ -1,56 +0,0 @@
-{% extends "coderedcms/pages/base.amp.html" %}
-{% load wagtailcore_tags wagtailimages_tags %}
-
-{% block amp_css %}
-    {{block.super}}
-    .mx-2 {
-        padding: 0 1em;
-    }
-    .lead {
-        font-size:1.2em;
-        font-weight: 200;
-    }
-{% endblock %}
-
-{% block content %}
-<article class="codered-article">
-    {% if self.cover_image %}
-    {% image self.cover_image fill-2000x1000 as cover_image %}
-    <div>
-        <amp-img src="{{cover_image.url}}" width="{{cover_image.width}}" height="{{cover_image.height}}" layout="responsive" ></amp-img>
-    </div>
-    {% endif %}
-    <div class="container">
-        <div class="article-header">
-            <h1>{{ self.title }}</h1>
-            {% if self.caption %}
-            <p class="lead">{{self.caption}}</p>
-            {% endif %}
-            <p>
-                {% if self.get_author_name %}
-                <span class="article-author">{{self.get_author_name}}</span>
-                <span class="mx-2">&bull;</span>
-                {% endif %}
-                <span class="article-date">{{self.get_pub_date}}</span>
-            </p>
-            <hr>
-        </div>
-        <div class="article-body">
-            {% include_block self.body with format='amp' settings=settings %}
-        </div>
-    </div>
-</article>
-
-{% endblock %}
-
-{% block footer %}
-  {% include 'coderedcms/snippets/footer.html' with format='amp' %}
-{% endblock %}
-
-{% block struct_seo %}
-    {% if settings.coderedcms.SeoSettings.struct_meta %}
-    <script type="application/ld+json">
-        {% include "coderedcms/includes/struct_data_article.json" with page=self %}
-    </script>
-    {% endif %}
-{% endblock %}

+ 5 - 54
coderedcms/templates/coderedcms/pages/article_page.html

@@ -2,43 +2,6 @@
 
 
 {% load wagtailadmin_tags wagtailcore_tags wagtailimages_tags coderedcms_tags %}
 {% load wagtailadmin_tags wagtailcore_tags wagtailimages_tags coderedcms_tags %}
 
 
-{% block description %}{{self.get_description}}{% endblock %}
-{% block html_seo_extra %}
-{% if self.get_author_name %}<meta name="author" content="{{self.get_author_name}}" />{% endif %}
-<meta name="created" content="{{self.get_pub_date|date:'c'}}" />
-<meta name="revised" content="{{self.last_published_at|date:'c'}}" />
-{% if settings.coderedcms.SeoSettings.amp_pages %}
-<link rel="amphtml" href="{{self.get_full_url}}?amp">
-{% endif %}
-{% endblock %}
-
-{% if settings.coderedcms.SeoSettings.og_meta %}
-    {% block og_description %}{{self.get_description}}{% endblock %}
-    {% block og_type %}article{% endblock %}
-    {% block og_seo_extra %}
-        <meta property="og:article:published_time" content="{{self.get_pub_date}}" />
-        <meta property="og:article:modified_time" content="{{self.last_published_at}}" />
-        {% if self.author_display %}
-        <meta property="og:article:author" content="{{self.author_display}}" />
-        {% elif self.author %}
-        <meta property="og:article:author:first_name" content="{{self.author.first_name}}" />
-        <meta property="og:article:author:last_name" content="{{self.author.last_name}}" />
-        {% endif %}
-    {% endblock %}
-{% endif %}
-
-{% if settings.coderedcms.SeoSettings.twitter_meta %}
-    {% block twitter_card %}{% if self.cover_image %}summary_large_image{% else %}{{block.super}}{% endif %}{% endblock %}
-    {% block twitter_seo_extra %}
-        {% if self.caption %}
-        <meta name="twitter:description" content="{{ self.caption }}">
-        {% endif %}
-        {% if self.author.twitter %}
-            <meta name="twitter:creator" content="{{self.author.twitter}}" />
-        {% endif %}
-    {% endblock %}
-{% endif %}
-
 {% block content %}
 {% block content %}
     <article class="codered-article {%if self.cover_image %}has-img{% endif %}">
     <article class="codered-article {%if self.cover_image %}has-img{% endif %}">
         {% if self.cover_image %}
         {% if self.cover_image %}
@@ -52,20 +15,16 @@
                 <p class="lead">{{self.caption}}</p>
                 <p class="lead">{{self.caption}}</p>
                 {% endif %}
                 {% endif %}
                 <p>
                 <p>
-                    {% if self.author_display %}
-                    <span class="article-author">{{self.get_author_name}}</span>
-                    {% elif self.author %}
+                    {% if self.author %}
+                    <img class="article-author-img rounded-circle mr-2" src="{% avatar_url self.author %}">
+                    {% elif self.owner %}
                     <img class="article-author-img rounded-circle mr-2" src="{% avatar_url self.author %}">
                     <img class="article-author-img rounded-circle mr-2" src="{% avatar_url self.author %}">
-                    <span class="article-author">{{self.get_author_name}}</span>
                     {% endif %}
                     {% endif %}
+                    <span class="article-author">{{self.seo_author}}</span>
 
 
-                    {% if self.get_author_name and self.get_pub_date %}
                     <span class="mx-2">&bull;</span>
                     <span class="mx-2">&bull;</span>
-                    {% endif %}
 
 
-                    {% if self.get_pub_date %}
-                    <span class="article-date">{{ self.get_pub_date }}</span>
-                    {% endif %}
+                    <span class="article-date">{{ self.seo_published_at }}</span>
                 </p>
                 </p>
                 <hr>
                 <hr>
             </div>
             </div>
@@ -75,11 +34,3 @@
         </div>
         </div>
     </article>
     </article>
 {% endblock %}
 {% endblock %}
-
-{% if settings.coderedcms.SeoSettings.struct_meta %}
-    {% block struct_seo_extra %}
-    <script type="application/ld+json">
-        {% include "coderedcms/includes/struct_data_article.json" with page=self %}
-    </script>
-    {% endblock %}
-{% endif %}

+ 3 - 11
coderedcms/templates/coderedcms/pages/article_page.search.html

@@ -19,20 +19,12 @@
         {% endif %}
         {% endif %}
 
 
         <p class="text-muted">
         <p class="text-muted">
-            {% if page.get_author_name %}
-            <span class="article-author">{{page.get_author_name}}</span>
-            {% endif %}
-
-            {% if page.get_author_name and page.get_pub_date %}
+            <span class="article-author">{{page.seo_author}}</span>
             <span class="mx-2">&bull;</span>
             <span class="mx-2">&bull;</span>
-            {% endif %}
-
-            {% if page.get_pub_date %}
-            <span class="article-date">{{ page.get_pub_date }}</span>
-            {% endif %}
+            <span class="article-date">{{ page.seo_published_at }}</span>
         </p>
         </p>
 
 
         <p>{{page.body_preview}}</p>
         <p>{{page.body_preview}}</p>
 
 
     </div>
     </div>
-</div>
+</div>

+ 0 - 234
coderedcms/templates/coderedcms/pages/base.amp.html

@@ -1,234 +0,0 @@
-{% load coderedcms_tags wagtailimages_tags %}
-
-<!doctype html>
-<html ⚡>
-  <head>
-    <meta charset="utf-8">
-    <title>{% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }} - {{self.get_site.site_name}}{% endif %}{% endblock %}{% block title_suffix %}{% endblock %}</title>
-    <link rel="canonical" href="{% block canonical %}{{self.get_full_url}}{% endblock %}">
-    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
-    <style amp-custom>
-        {% block amp_css %}
-        /* Some really basic Bootstrap styles */
-        body {
-            font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
-            margin: 0 auto;
-        }
-        hr {
-            margin-top: 1rem;
-            margin-bottom: 1rem;
-            border: 0;
-            border-top: 1px solid rgba(0, 0, 0, 0.1);
-        }
-        .container {
-            max-width:960px;
-            padding: 0 15px;
-            margin:auto;
-        }
-
-        .navbar {
-        position: relative;
-        display: -ms-flexbox;
-        display: flex;
-        -ms-flex-wrap: wrap;
-        flex-wrap: wrap;
-        -ms-flex-align: center;
-        align-items: center;
-        -ms-flex-pack: justify;
-        justify-content: center;
-        padding: 0.5rem 1rem;
-        }
-
-        .navbar-light .navbar-brand {
-        color: rgba(0, 0, 0, 0.9);
-        }
-
-        .navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {
-        color: rgba(0, 0, 0, 0.9);
-        }
-
-        .navbar-light .navbar-nav .nav-link {
-        color: rgba(0, 0, 0, 0.5);
-        }
-
-        .navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {
-        color: rgba(0, 0, 0, 0.7);
-        }
-
-        .navbar-light .navbar-nav .nav-link.disabled {
-        color: rgba(0, 0, 0, 0.3);
-        }
-
-        .navbar-light .navbar-nav .show > .nav-link,
-        .navbar-light .navbar-nav .active > .nav-link,
-        .navbar-light .navbar-nav .nav-link.show,
-        .navbar-light .navbar-nav .nav-link.active {
-        color: rgba(0, 0, 0, 0.9);
-        }
-
-        .navbar-light .navbar-text {
-        color: rgba(0, 0, 0, 0.5);
-        }
-
-        .navbar-light .navbar-text a {
-        color: rgba(0, 0, 0, 0.9);
-        }
-
-        .navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {
-        color: rgba(0, 0, 0, 0.9);
-        }
-
-        .navbar-dark .navbar-brand {
-        color: #fff;
-        }
-
-        .navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {
-        color: #fff;
-        }
-
-        .navbar-dark .navbar-nav .nav-link {
-        color: rgba(255, 255, 255, 0.5);
-        }
-
-        .navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {
-        color: rgba(255, 255, 255, 0.75);
-        }
-
-        .navbar-dark .navbar-nav .nav-link.disabled {
-        color: rgba(255, 255, 255, 0.25);
-        }
-
-        .navbar-dark .navbar-nav .show > .nav-link,
-        .navbar-dark .navbar-nav .active > .nav-link,
-        .navbar-dark .navbar-nav .nav-link.show,
-        .navbar-dark .navbar-nav .nav-link.active {
-        color: #fff;
-        }
-
-        .navbar-dark .navbar-text {
-        color: rgba(255, 255, 255, 0.5);
-        }
-
-        .navbar-dark .navbar-text a {
-        color: #fff;
-        }
-
-        .navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {
-        color: #fff;
-        }
-
-        .bg-primary {
-        background-color: #007bff;
-        }
-
-        a.bg-primary:hover, a.bg-primary:focus,
-        button.bg-primary:hover,
-        button.bg-primary:focus {
-        background-color: #0062cc;
-        }
-
-        .bg-secondary {
-        background-color: #6c757d;
-        }
-
-        a.bg-secondary:hover, a.bg-secondary:focus,
-        button.bg-secondary:hover,
-        button.bg-secondary:focus {
-        background-color: #545b62;
-        }
-
-        .bg-success {
-        background-color: #28a745;
-        }
-
-        a.bg-success:hover, a.bg-success:focus,
-        button.bg-success:hover,
-        button.bg-success:focus {
-        background-color: #1e7e34;
-        }
-
-        .bg-info {
-        background-color: #17a2b8;
-        }
-
-        a.bg-info:hover, a.bg-info:focus,
-        button.bg-info:hover,
-        button.bg-info:focus {
-        background-color: #117a8b;
-        }
-
-        .bg-warning {
-        background-color: #ffc107;
-        }
-
-        a.bg-warning:hover, a.bg-warning:focus,
-        button.bg-warning:hover,
-        button.bg-warning:focus {
-        background-color: #d39e00;
-        }
-
-        .bg-danger {
-        background-color: #dc3545;
-        }
-
-        a.bg-danger:hover, a.bg-danger:focus,
-        button.bg-danger:hover,
-        button.bg-danger:focus {
-        background-color: #bd2130;
-        }
-
-        .bg-light {
-        background-color: #f8f9fa;
-        }
-
-        a.bg-light:hover, a.bg-light:focus,
-        button.bg-light:hover,
-        button.bg-light:focus {
-        background-color: #dae0e5;
-        }
-
-        .bg-dark {
-        background-color: #343a40;
-        }
-
-        a.bg-dark:hover, a.bg-dark:focus,
-        button.bg-dark:hover,
-        button.bg-dark:focus {
-        background-color: #1d2124;
-        }
-
-        .bg-white {
-        background-color: #fff;
-        }
-
-        .bg-transparent {
-        background-color: transparent;
-        }
-        {% endblock %}
-    </style>
-    <style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
-    <script async src="https://cdn.ampproject.org/v0.js"></script>
-    <script async custom-element="amp-iframe" src="https://cdn.ampproject.org/v0/amp-iframe-0.1.js"></script>
-  </head>
-  <body>
-    {% block amp_nav %}
-    <nav class="navbar {% get_navbar_css %}">
-        <a class="navbar-brand" href="/">
-            {% if settings.coderedcms.LayoutSettings.logo %}
-            {% image settings.coderedcms.LayoutSettings.logo max-300x50 as logo %}
-            <amp-img src="{{logo.url}}" width="{{logo.width}}" height={{logo.height}} alt="{{self.get_site.site_name}}" />
-            {% else %}
-            {{self.get_site.site_name}}
-            {% endif %}
-        </a>
-    </nav>
-    {% endblock %}
-
-    {% block content %}{% endblock %}
-
-    {% block footer %}{% endblock %}
-
-    {% block struct_seo %}{% endblock %}
-
-  </body>
-</html>

+ 3 - 44
coderedcms/templates/coderedcms/pages/base.html

@@ -35,39 +35,8 @@
         <meta http-equiv="X-UA-Compatible" content="IE=edge" />
         <meta http-equiv="X-UA-Compatible" content="IE=edge" />
         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 
 
-        {# HTML SEO #}
-        {% block html_seo_base %}
-        <title>{% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}{% endblock %}{% block title_suffix %}{% if not self.seo_title %} - {{site.site_name}}{% endif %}{% endblock %}</title>
-        <meta name="description" content="{% block description %}{% if self.search_description %}{{ self.search_description }}{% endif %}{% endblock %}" />
-        <link rel="canonical" href="{% block canonical %}{{self.get_full_url}}{% endblock %}">
-        {% endblock %}
-        {% block html_seo_extra %}{% endblock %}
-
-        {# Open Graph SEO #}
-        {% if settings.coderedcms.SeoSettings.og_meta %}
-            {% block og_seo_base %}
-            <meta property="og:title" content="{% block og_title %}{% if self.seo_title %}{{self.seo_title}}{% else %}{{self.title}}{% endif %}{% endblock %}" />
-            <meta property="og:description" content="{% block og_description %}{% if self.search_description %}{{ self.search_description }}{% endif %}{% endblock %}" />
-            <meta property="og:site_name" content="{% block og_site_name %}{{self.get_site.site_name}}{% endblock %}" />
-            <meta property="og:type" content="{% block og_type %}website{% endblock %}" />
-            <meta property="og:url" content="{% block og_url %}{{self.get_full_url}}{% endblock %}" />
-            <meta property="og:image" content="{% block og_image %}{% og_image self %}{% endblock %}" />
-            {% endblock %}
-            {% block og_seo_extra %}{% endblock %}
-        {% endif %}
-
-        {# Twitter SEO #}
-        {% if settings.coderedcms.SeoSettings.twitter_meta %}
-            {% block twitter_seo_base %}
-            <meta name="twitter:card" content="{% block twitter_card %}summary{% endblock %}" />
-            <meta name="twitter:title" content="{{self.title}}">
-            <meta name="twitter:image" content="{% og_image self %}">
-            {% if settings.coderedcms.SocialMediaSettings.twitter %}
-            <meta name="twitter:site" content="{% block twitter_site %}@{{settings.coderedcms.SocialMediaSettings.twitter_handle}}{% endblock %}" />
-            {% endif %}
-            {% endblock %}
-            {% block twitter_seo_extra %}{% endblock %}
-        {% endif %}
+        {# SEO Metadata #}
+        {% include "wagtailseo/meta.html" %}
 
 
         {% block frontend_assets %}
         {% block frontend_assets %}
             {% if settings.coderedcms.LayoutSettings.frontend_theme %}
             {% if settings.coderedcms.LayoutSettings.frontend_theme %}
@@ -188,17 +157,7 @@
         <script type="text/javascript" src="{% static 'js/custom.js' %}"></script>
         <script type="text/javascript" src="{% static 'js/custom.js' %}"></script>
         {% endblock %}
         {% endblock %}
 
 
-        {# Structured data JSON-LD #}
-        {% if settings.coderedcms.SeoSettings.struct_meta %}
-            {% block struct_seo_base %}
-                {% if self.struct_org_type %}
-                <script type="application/ld+json">
-                    {% include "coderedcms/includes/struct_data_org.json" with page=self %}
-                </script>
-                {% endif %}
-            {% endblock %}
-            {% block struct_seo_extra %}{% endblock %}
-        {% endif %}
+        {% include "wagtailseo/struct_data.html" %}
 
 
         {% block body_tracking_scripts %}
         {% block body_tracking_scripts %}
         {% if settings.coderedcms.AnalyticsSettings.body_scripts %}
         {% if settings.coderedcms.AnalyticsSettings.body_scripts %}

+ 5 - 8
coderedcms/templates/coderedcms/pages/event_page.html

@@ -1,10 +1,6 @@
 {% extends "coderedcms/pages/web_page.html" %}
 {% extends "coderedcms/pages/web_page.html" %}
 {% load wagtailadmin_tags wagtailcore_tags wagtailimages_tags coderedcms_tags %}
 {% load wagtailadmin_tags wagtailcore_tags wagtailimages_tags coderedcms_tags %}
 
 
-{% if settings.coderedcms.SeoSettings.twitter_meta %}
-    {% block twitter_card %}{% if self.cover_image %}summary_large_image{% else %}{{block.super}}{% endif %}{% endblock %}
-{% endif %}
-
 {% block content_pre_body %}
 {% block content_pre_body %}
     {{ block.super }}
     {{ block.super }}
     {% with self.most_recent_occurrence as nextup %}
     {% with self.most_recent_occurrence as nextup %}
@@ -56,10 +52,11 @@
 
 
 {% endblock %}
 {% endblock %}
 
 
-{% if settings.coderedcms.SeoSettings.struct_meta %}
-    {% block struct_seo_extra %}
+{% block struct_seo_extra %}
+{{ block.super }}
+{% if settings.wagtailseo.SeoSettings.struct_meta %}
     <script type="application/ld+json">
     <script type="application/ld+json">
-        {% include "coderedcms/includes/struct_data_event.json" with page=self %}
+        {{ self.seo_struct_event_json | safe }}
     </script>
     </script>
-    {% endblock %}
 {% endif %}
 {% endif %}
+{% endblock %}

+ 1 - 54
coderedcms/templatetags/coderedcms_tags.py

@@ -1,16 +1,12 @@
 import string
 import string
 import random
 import random
-import re
 
 
 from bs4 import BeautifulSoup
 from bs4 import BeautifulSoup
-from datetime import datetime
 from django import template
 from django import template
 from django.conf import settings
 from django.conf import settings
 from django.forms import ClearableFileInput
 from django.forms import ClearableFileInput
 from django.utils.html import mark_safe
 from django.utils.html import mark_safe
-from wagtail.core.models import Collection, Site
-from wagtail.core.rich_text import RichText
-from wagtail.core.templatetags.wagtailcore_tags import richtext
+from wagtail.core.models import Collection
 from wagtail.images.models import Image
 from wagtail.images.models import Image
 
 
 from coderedcms import utils, __version__
 from coderedcms import utils, __version__
@@ -44,32 +40,6 @@ def generate_random_id():
     return "cr-{}".format(value)
     return "cr-{}".format(value)
 
 
 
 
-@register.simple_tag(takes_context=True)
-def og_image(context, page):
-
-    # Fixes #240 https://github.com/coderedcorp/coderedcms/issues/240
-    # Prepend the site's root URL except for when MEDIA_URL already
-    # looks like a full URL.
-    protocol = re.compile(r'^(\w[\w\.\-\+]*:)*//')
-
-    if protocol.match(settings.MEDIA_URL):
-        base_url = ''
-    else:
-        base_url = Site.find_for_request(context['request']).root_url
-
-    if page:
-        if page.og_image:
-            return base_url + page.og_image.get_rendition('original').url
-        elif page.cover_image:
-            return base_url + page.cover_image.get_rendition('original').url
-
-    layout_settings = LayoutSettings.for_request(context['request'])
-    if layout_settings.logo:
-        return base_url + layout_settings.logo.get_rendition('original').url
-
-    return None
-
-
 @register.simple_tag
 @register.simple_tag
 def is_menu_item_dropdown(value):
 def is_menu_item_dropdown(value):
     return \
     return \
@@ -171,29 +141,6 @@ def query_update(querydict, key=None, value=None):
     return get
     return get
 
 
 
 
-@register.filter
-def structured_data_datetime(dt):
-    """
-    Formats datetime object to structured data compatible datetime string.
-    """
-    try:
-        if dt.time():
-            return datetime.strftime(dt, "%Y-%m-%dT%H:%M")
-        return datetime.strftime(dt, "%Y-%m-%d")
-    except AttributeError:
-        return ""
-
-
-@register.filter
-def convert_to_amp(value):
-    """
-    Converts HTML to AMP.
-    """
-    if isinstance(value, RichText):
-        value = richtext(value.source)
-    return mark_safe(utils.convert_to_amp(value))
-
-
 @register.simple_tag
 @register.simple_tag
 def render_iframe_from_embed(embed):
 def render_iframe_from_embed(embed):
     soup = BeautifulSoup(embed.html, "html.parser")
     soup = BeautifulSoup(embed.html, "html.parser")

+ 1 - 0
coderedcms/tests/settings.py

@@ -37,6 +37,7 @@ INSTALLED_APPS = [
     'wagtailfontawesome',
     'wagtailfontawesome',
     'wagtailcache',
     'wagtailcache',
     'wagtailimportexport',
     'wagtailimportexport',
+    'wagtailseo',
 
 
     # Wagtail
     # Wagtail
     'wagtail.contrib.forms',
     'wagtail.contrib.forms',

+ 0 - 31
coderedcms/tests/test_utils.py

@@ -1,31 +0,0 @@
-from django.test import TestCase
-from coderedcms.utils import convert_to_amp
-
-
-class AMPConversionTestCase(TestCase):
-    """
-    Test case for AMP tag conversions
-    """
-
-    def setUp(self):
-        self.unprocessed_img_html = """<img src="/source.jpg" /><img src="/source2.jpg" />"""  # noqa
-        self.unprocessed_iframe_html = """<iframe src="/source.html"></iframe><iframe src="/source-2.html"></iframe>"""  # noqa
-
-        self.processed_amp_img_html = """<amp-img src="/source.jpg"></amp-img><amp-img src="/source2.jpg"></amp-img>"""  # noqa
-        self.processed_amp_iframe_html = """<amp-iframe layout="responsive" src="/source.html"></amp-iframe><amp-iframe layout="responsive" src="/source-2.html"></amp-iframe>"""  # noqa
-
-    def test_img_processing(self):
-        """
-        Test to verify img tags are converted to amp-img tags.
-        """
-        processed_html = convert_to_amp(self.unprocessed_img_html, pretty=False)
-        self.assertEqual(processed_html, self.processed_amp_img_html)
-
-    def test_iframe_processing(self):
-        """
-        Test to verify iframe tags are converted to amp-iframe tags.
-        """
-        processed_html = convert_to_amp(self.unprocessed_iframe_html, pretty=False)
-        print(self.processed_amp_iframe_html)
-        print(processed_html)
-        self.assertEqual(processed_html, self.processed_amp_iframe_html)

+ 0 - 1
coderedcms/tests/testapp/models.py

@@ -27,7 +27,6 @@ class ArticlePage(CoderedArticlePage):
     parent_page_types = ['testapp.ArticleIndexPage']
     parent_page_types = ['testapp.ArticleIndexPage']
 
 
     template = 'coderedcms/pages/article_page.html'
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.html'
     search_template = 'coderedcms/pages/article_page.search.html'
     search_template = 'coderedcms/pages/article_page.search.html'
 
 
 
 

+ 0 - 35
coderedcms/utils.py

@@ -1,4 +1,3 @@
-from bs4 import BeautifulSoup
 from django.core.validators import URLValidator
 from django.core.validators import URLValidator
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.utils.html import mark_safe
 from django.utils.html import mark_safe
@@ -47,37 +46,3 @@ def fix_ical_datetime_format(dt_str):
         dt_str = dt_str[:-3] + dt_str[-2:]
         dt_str = dt_str[:-3] + dt_str[-2:]
         return dt_str
         return dt_str
     return dt_str
     return dt_str
-
-
-def convert_to_amp(value, pretty=True):
-    """
-    Function that converts non-amp compliant html to valid amp html.
-    value must be a string
-    """
-    soup = BeautifulSoup(value, "html.parser")
-
-    # Replace img tags with amp-img
-    try:
-        img_tags = soup.find_all('img')
-        for img_tag in img_tags:
-            # Force the tag to be non-self-closing i.e. <img.../> becomes <amp-img...></amp-img>
-            img_tag.can_be_empty_element = False
-            # Change tag name from 'img' to 'amp-img'
-            img_tag.name = 'amp-img'
-    except AttributeError:
-        pass
-
-    # Replace iframe tags with amp-iframe
-    try:
-        iframe_tags = soup.find_all('iframe')
-        for iframe_tag in iframe_tags:
-            iframe_tag.name = 'amp-iframe'
-            iframe_tag['layout'] = 'responsive'
-
-    except AttributeError:
-        pass
-
-    if pretty:
-        return soup.prettify()
-
-    return str(soup)

+ 0 - 1
docs/advanced/advanced02.rst

@@ -79,7 +79,6 @@ frameworks that we are using.
         parent_page_types = ['website.ArticleIndexPage']
         parent_page_types = ['website.ArticleIndexPage']
 
 
         template = 'coderedcms/pages/article_page.html'
         template = 'coderedcms/pages/article_page.html'
-        amp_template = 'coderedcms/pages/article_page.amp.html'
         search_template = 'coderedcms/pages/article_page.search.html'
         search_template = 'coderedcms/pages/article_page.search.html'
 
 
 
 

+ 54 - 2
docs/releases/v0.21.0.rst

@@ -18,12 +18,64 @@ New features
 * Improve developer tooling in new CodeRed CMS sites by providing pre-configured
 * Improve developer tooling in new CodeRed CMS sites by providing pre-configured
   ``.gitattribute`` and ``.editorconfig`` files.
   ``.gitattribute`` and ``.editorconfig`` files.
 
 
+* SEO features have been refactored into separate package ``wagtail-seo``. There
+  should be no noticeable changes to your models, except for the addition of new
+  properties which can be easily overridden and customized. See
+  `wagtail-seo customization docs <https://docs.coderedcorp.com/wagtail-seo/customizing/index.html>`_.
+
 
 
 Upgrade considerations
 Upgrade considerations
 ----------------------
 ----------------------
 
 
-* ``coderedcms.blocks.MultiSelectBlock`` has been removed and is now replaced
-  with ``wagtail.core.blocks.MultipleChoiceBlock``.
+* Existing projects will need to add ``wagtailseo,`` to ``INSTALLED_APPS``
+  (usually in ``settings/base.py``).
+
+* **AMP support has been removed**. This means that your site will no longer
+  serve automatically generated AMP pages after upgrading. If you had previously
+  implemented custom AMP templates, or rely heavily on AMP pages, you may need
+  to implement this functionality yourself. For any site what is not a large
+  news publisher, we recommend removing AMP from your site, as it is no longer
+  worth the maintenance burden. `See reasoning for this change
+  <https://github.com/coderedcorp/wagtail-seo/issues/21>`_.
+
+  * ``coderedcms_tags.convert_to_amp`` has been removed.
+
+  * ``coderedcms.utils.convert_to_amp()`` has been removed.
+
+  * You should remove any ``amp_template`` attributes from your models, as they
+    are no longer used.
+
+* ``coderedcms/pages/base.html`` template has been modified. If you implement or
+  extend your own ``base.html``, the following changes are notable:
+
+  * template blocks: ``html_seo_base``, ``html_seo_extra``, ``og_seo_base``,
+    ``og_seo_extra``, ``twitter_seo_base``, ``twitter_seo_extra`` have been
+    replaced with ``{% include "wagtailseo/meta.html" %}``
+
+  * template blocks: ``struct_seo_base``, ``struct_seo_extra`` have been
+    replaced with ``{% include "wagtailseo/struct_data.html" %}``
+
+* Properties and templates associated with SEO functionality have changed:
+
+  * ``CoderedArticlePage.get_author_name`` -> ``CoderedArticlePage.seo_author``
+
+  * ``CoderedArticlePage.get_pub_date`` -> ``CoderedArticlePage.seo_published_at``
+
+  * ``CoderedArticlePage.get_description`` -> ``CoderedArticlePage.seo_description``
+
+  * ``coderedcms_tags.og_image`` -> ``page.seo_image_url``
+
+  * ``coderedcms_tags.structured_data_datetime`` has been removed.
+
+* Some built-in blocks used in the Wagtail admin have been replaced:
+
+  * ``coderedcms.blocks.MultiSelectBlock`` -> ``wagtail.core.blocks.MultipleChoiceBlock``
+
+  * ``coderedcms.blocks.OpenHoursValue`` -> ``wagtailseo.blocks.OpenHoursValue``
+
+  * ``coderedcms.blocks.OpenHoursBlock`` -> ``wagtailseo.blocks.OpenHoursBlock``
+
+  * ``coderedcms.blocks.StructuredDataActionBlock`` -> ``wagtailseo.blocks.StructuredDataActionBlock``
 
 
 * CodeRed CMS now sets ``default_auto_field = 'django.db.models.AutoField'`` for
 * CodeRed CMS now sets ``default_auto_field = 'django.db.models.AutoField'`` for
   its own concrete models. If you had previously manually specified a different
   its own concrete models. If you had previously manually specified a different

+ 2 - 1
setup.py

@@ -54,7 +54,8 @@ setup(
         'wagtail==2.13.*',
         'wagtail==2.13.*',
         'wagtailfontawesome>=1.2.*',
         'wagtailfontawesome>=1.2.*',
         'wagtail-cache==1.*',
         'wagtail-cache==1.*',
-        'wagtail-import-export>=0.2,<0.3'
+        'wagtail-import-export>=0.2,<0.3',
+        'wagtail-seo==1.*',
     ],
     ],
     entry_points={
     entry_points={
         "console_scripts": [
         "console_scripts": [

+ 1 - 0
tutorial/mysite/mysite/settings/base.py

@@ -36,6 +36,7 @@ INSTALLED_APPS = [
     'wagtailfontawesome',
     'wagtailfontawesome',
     'wagtailcache',
     'wagtailcache',
     'wagtailimportexport',
     'wagtailimportexport',
+    'wagtailseo',
 
 
     # Wagtail
     # Wagtail
     'wagtail.contrib.forms',
     'wagtail.contrib.forms',

+ 0 - 1
tutorial/mysite/website/models.py

@@ -29,7 +29,6 @@ class ArticlePage(CoderedArticlePage):
     parent_page_types = ['website.ArticleIndexPage']
     parent_page_types = ['website.ArticleIndexPage']
 
 
     template = 'coderedcms/pages/article_page.html'
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.html'
     search_template = 'coderedcms/pages/article_page.search.html'
     search_template = 'coderedcms/pages/article_page.search.html'