2
0
Эх сурвалжийг харах

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

Vince Salvino 3 жил өмнө
parent
commit
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,
     HeroBlock
 )
-from .metadata_blocks import (  # noqa
-    OpenHoursBlock,
-    StructuredDataActionBlock
-)
 from .base_blocks import (  # noqa
     BaseBlock,
     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 logging
 import os
+import warnings
+from datetime import datetime
+from typing import Optional, TYPE_CHECKING
+
 import geocoder
 from django import forms
 from django.conf import settings
@@ -33,7 +37,6 @@ from modelcluster.tags import ClusterTaggableManager
 from pathlib import Path
 from taggit.models import TaggedItemBase
 from wagtail.admin.edit_handlers import (
-    HelpPanel,
     FieldPanel,
     FieldRowPanel,
     InlinePanel,
@@ -55,15 +58,15 @@ from wagtail.contrib.forms.models import FormSubmission
 from wagtail.search import index
 from wagtail.utils.decorators import cached_classmethod
 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 (
     CONTENT_STREAMBLOCKS,
     LAYOUT_STREAMBLOCKS,
     STREAMFORM_BLOCKS,
     ContentWallBlock,
-    OpenHoursBlock,
-    StructuredDataActionBlock,
 )
 from coderedcms.fields import ColorField
 from coderedcms.forms import CoderedFormBuilder, CoderedSubmissionsListView
@@ -72,7 +75,6 @@ from coderedcms.models.wagtailsettings_models import (
     GeneralSettings,
     GoogleApiSettings,
     LayoutSettings,
-    SeoSettings,
 )
 from coderedcms.wagtail_flexible_forms.blocks import FormFieldBlock, FormStepBlock
 from coderedcms.wagtail_flexible_forms.models import (
@@ -87,6 +89,10 @@ from coderedcms.settings import cr_settings
 from coderedcms.widgets import ClassifierSelectWidget
 
 
+if TYPE_CHECKING:
+    from wagtail.images.models import AbstractImage
+
+
 logger = logging.getLogger('coderedcms')
 
 
@@ -100,8 +106,6 @@ def get_page_models():
 class CoderedPageMeta(PageBase):
     def __init__(cls, 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:
             cls.search_db_include = False
         if 'search_db_boost' not in dct:
@@ -124,7 +128,7 @@ class CoderedTag(TaggedItemBase):
     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.
     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:
     #
     # template = ''
-    # amp_template = ''
     # ajax_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
@@ -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 + [
         StreamFieldPanel('content_walls'),
@@ -504,41 +362,50 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
 
         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:
             layout_settings = LayoutSettings.for_site(self.get_site())
             if layout_settings.logo:
                 return layout_settings.logo
         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):
         """
         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:
             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):
         """
@@ -615,6 +482,7 @@ class CoderedPage(WagtailCacheMixin, Page, metaclass=CoderedPageMeta):
         context['content_walls'] = self.get_content_walls(check_child_setting=False)
         return context
 
+
 ###############################################################################
 # Abstract pages providing pre-built common website functionality, suitable for subclassing.
 # These are abstract so subclasses can override fields if desired.
@@ -681,7 +549,6 @@ class CoderedArticlePage(CoderedWebPage):
         abstract = True
 
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.html'
 
     # Override body to provide simpler content
     body = StreamField(CONTENT_STREAMBLOCKS, null=True, blank=True)
@@ -712,25 +579,57 @@ class CoderedArticlePage(CoderedWebPage):
     )
 
     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.
         """
         if self.author_display:
             return self.author_display
         if self.author:
             return self.author.get_full_name()
-        return ''
+        if self.owner:
+            return self.owner.get_full_name()
+        return ""
 
     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.
         """
         if self.date_display:
             return self.date_display
-        return ''
+        return self.first_published_at
 
     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.
         """
         if self.search_description:
@@ -739,7 +638,7 @@ class CoderedArticlePage(CoderedWebPage):
             return self.caption
         if self.body_preview:
             return self.body_preview
-        return ''
+        return ""
 
     search_fields = (
         CoderedWebPage.search_fields +
@@ -869,6 +768,43 @@ class CoderedEventPage(CoderedWebPage, BaseEvent):
             if occurrences:
                 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):
         """
         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 wagtail.tests.utils import WagtailPageTests
-from wagtail.core.models import Site
 
 from coderedcms.models.page_models import (
     CoderedArticleIndexPage,
@@ -26,9 +25,6 @@ from coderedcms.tests.testapp.models import (
     StreamFormPage,
     WebPage
 )
-from coderedcms.models.wagtailsettings_models import (
-    SeoSettings
-)
 
 
 class BasicPageTestCase():
@@ -165,15 +161,6 @@ class CoderedStreamFormPageTestCase(AbstractPageTestCase, WagtailPageTests):
 class ArticlePageTestCase(ConcreteBasicPageTestCase, WagtailPageTests):
     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):
     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
 from django.db import models
 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.contrib.settings.models import BaseSetting, register_setting
 from wagtail.images import get_image_model_string
@@ -330,50 +330,6 @@ class GeneralSettings(BaseSetting):
         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')
 class GoogleApiSettings(BaseSetting):
     """

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

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

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

@@ -24,7 +24,6 @@ class ArticlePage(CoderedArticlePage):
     parent_page_types = ['website.ArticleIndexPage']
 
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.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',
     'wagtailcache',
     'wagtailimportexport',
+    'wagtailseo',
 
     # Wagtail
     'wagtail.contrib.forms',

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

@@ -24,7 +24,6 @@ class ArticlePage(CoderedArticlePage):
     parent_page_types = ['website.ArticleIndexPage']
 
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.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 %}
     <div class="card-body">
         <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>
         {% if self.show_preview %}
         <p class="card-text">{{article.body_preview}}</p>
         {% endif %}
         <a href="{{article.url}}" title="{{article.title}}">Read more &raquo;</a>
     </div>
-</div>
+</div>

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

@@ -1,12 +1,5 @@
 {% extends "coderedcms/blocks/base_block.html" %}
-{% load coderedcms_tags %}
 
 {% 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 %}
         <div class="media-body">
             <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 %}
             <p>{{article.body_preview}}</p>
             {% 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 %}
-
-{% if format == 'amp' %}
-{{ self|richtext|convert_to_amp }}
-{% else %}
 {{ self|richtext }}
-{% endif %}
-
 {% 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">
                 <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_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 %}
             </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 %}
 
-{% 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 %}
     <article class="codered-article {%if self.cover_image %}has-img{% endif %}">
         {% if self.cover_image %}
@@ -52,20 +15,16 @@
                 <p class="lead">{{self.caption}}</p>
                 {% endif %}
                 <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 %}">
-                    <span class="article-author">{{self.get_author_name}}</span>
                     {% 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>
-                    {% 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>
                 <hr>
             </div>
@@ -75,11 +34,3 @@
         </div>
     </article>
 {% 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 %}
 
         <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>
-            {% 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>{{page.body_preview}}</p>
 
     </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 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 %}
             {% if settings.coderedcms.LayoutSettings.frontend_theme %}
@@ -188,17 +157,7 @@
         <script type="text/javascript" src="{% static 'js/custom.js' %}"></script>
         {% 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 %}
         {% 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" %}
 {% 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.super }}
     {% with self.most_recent_occurrence as nextup %}
@@ -56,10 +52,11 @@
 
 {% 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">
-        {% include "coderedcms/includes/struct_data_event.json" with page=self %}
+        {{ self.seo_struct_event_json | safe }}
     </script>
-    {% endblock %}
 {% endif %}
+{% endblock %}

+ 1 - 54
coderedcms/templatetags/coderedcms_tags.py

@@ -1,16 +1,12 @@
 import string
 import random
-import re
 
 from bs4 import BeautifulSoup
-from datetime import datetime
 from django import template
 from django.conf import settings
 from django.forms import ClearableFileInput
 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 coderedcms import utils, __version__
@@ -44,32 +40,6 @@ def generate_random_id():
     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
 def is_menu_item_dropdown(value):
     return \
@@ -171,29 +141,6 @@ def query_update(querydict, key=None, value=None):
     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
 def render_iframe_from_embed(embed):
     soup = BeautifulSoup(embed.html, "html.parser")

+ 1 - 0
coderedcms/tests/settings.py

@@ -37,6 +37,7 @@ INSTALLED_APPS = [
     'wagtailfontawesome',
     'wagtailcache',
     'wagtailimportexport',
+    'wagtailseo',
 
     # Wagtail
     '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']
 
     template = 'coderedcms/pages/article_page.html'
-    amp_template = 'coderedcms/pages/article_page.amp.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.exceptions import ValidationError
 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:]
         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']
 
         template = 'coderedcms/pages/article_page.html'
-        amp_template = 'coderedcms/pages/article_page.amp.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
   ``.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
 ----------------------
 
-* ``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
   its own concrete models. If you had previously manually specified a different

+ 2 - 1
setup.py

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

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

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

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

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