import hashlib
import mimetypes
from urllib.parse import unquote

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.http import HttpResponseRedirect
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.fields import CreationDateTimeField, ModificationDateTimeField
from great_components.mixins import GA360Mixin
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.models import ClusterableModel, ParentalKey
from taggit.managers import TaggableManager
from taggit.models import ItemBase, TagBase, TaggedItemBase
from wagtail.admin.edit_handlers import (
    FieldPanel,
    InlinePanel,
    MultiFieldPanel,
    ObjectList,
    PageChooserPanel,
    StreamFieldPanel,
    TabbedInterface,
)
from wagtail.contrib.redirects.models import Redirect
from wagtail.contrib.settings.models import BaseSetting, register_setting
from wagtail.core import blocks
from wagtail.core.blocks.stream_block import StreamBlockValidationError
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.models import Orderable, Page
from wagtail.images import get_image_model_string
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.images.models import AbstractImage, AbstractRendition, Image
from wagtail.snippets.models import register_snippet
from wagtail.utils.decorators import cached_classmethod
from wagtailmedia.models import Media

from core import blocks as core_blocks, mixins
from core.case_study_index import delete_cs_index, update_cs_index
from core.constants import BACKLINK_QUERYSTRING_NAME, RICHTEXT_FEATURES__MINIMAL
from core.context import get_context_provider
from core.utils import PageTopicHelper, get_first_lesson
from exportplan.core.data import (
    SECTION_SLUGS as EXPORTPLAN_SLUGS,
    SECTIONS as EXPORTPLAN_URL_MAP,
)

# If we make a Redirect appear as a Snippet, we can sync it via Wagtail-Transfer
register_snippet(Redirect)


class GreatMedia(Media):

    transcript = models.TextField(
        verbose_name=_('Transcript'), blank=False, null=True  # left null because was an existing field
    )

    subtitles_en = models.TextField(
        verbose_name=_('English subtitles'),
        null=True,
        blank=True,
        help_text='English-language subtitles for this video, in VTT format',
    )

    admin_form_fields = Media.admin_form_fields + (
        'transcript',
        'subtitles_en',
    )

    @property
    def sources(self):
        return [
            {
                'src': self.url,
                'type': mimetypes.guess_type(self.filename)[0] or 'application/octet-stream',
                'transcript': self.transcript,
            }
        ]

    @property
    def subtitles(self):
        output = []
        # TO COME: support for more than just English
        if self.subtitles_en:
            output.append(
                {
                    'srclang': 'en',
                    'label': 'English',
                    'url': reverse('core:subtitles-serve', args=[self.id, 'en']),
                    'default': False,
                },
            )
        return output


class AbstractObjectHash(models.Model):
    class Meta:
        abstract = True

    content_hash = models.CharField(max_length=1000)

    @staticmethod
    def generate_content_hash(field_file):
        filehash = hashlib.md5()
        field_file.open()
        filehash.update(field_file.read())
        field_file.close()
        return filehash.hexdigest()


class DocumentHash(AbstractObjectHash):
    document = models.ForeignKey(
        'wagtaildocs.Document', null=True, blank=True, on_delete=models.CASCADE, related_name='+'
    )


class ImageHash(AbstractObjectHash):
    image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.CASCADE, related_name='+')


class AltTextImage(AbstractImage):
    alt_text = models.CharField(max_length=255, blank=True)

    admin_form_fields = Image.admin_form_fields + ('alt_text',)


class Rendition(AbstractRendition):
    image = models.ForeignKey(AltTextImage, on_delete=models.CASCADE, related_name='renditions')

    class Meta:
        unique_together = ('image', 'filter_spec', 'focal_point_key')

    @property
    def alt(self):
        return self.image.alt_text


@register_snippet
class Tour(ClusterableModel):
    page = models.OneToOneField('wagtailcore.Page', on_delete=models.CASCADE, related_name='tour')
    title = models.CharField(max_length=255)
    body = models.CharField(max_length=255)
    button_text = models.CharField(max_length=255)

    panels = [
        PageChooserPanel('page'),
        FieldPanel('title'),
        FieldPanel('body'),
        FieldPanel('button_text'),
        MultiFieldPanel([InlinePanel('steps')], heading='Steps'),
    ]

    def __str__(self):
        return self.page.title


class TourStep(Orderable):
    title = models.CharField(max_length=255)
    body = models.CharField(max_length=255)
    position = models.CharField(max_length=255)
    selector = models.CharField(max_length=255)
    tour = ParentalKey(Tour, on_delete=models.CASCADE, related_name='steps')

    panels = [
        FieldPanel('title'),
        FieldPanel('body'),
        FieldPanel('position'),
        FieldPanel('selector'),
    ]


@register_snippet
class Product(models.Model):
    name = models.CharField(max_length=255)

    panels = [
        FieldPanel('name'),
    ]

    def __str__(self):
        return self.name


@register_snippet
class Region(models.Model):
    name = models.CharField(max_length=100, unique=True)

    panels = [FieldPanel('name')]

    class Meta:
        ordering = ('name',)

    def __str__(self):
        return self.name


@register_snippet
class Country(models.Model):
    name = models.CharField(max_length=255)
    slug = models.SlugField(max_length=100, unique=True)
    region = models.ForeignKey(Region, null=True, blank=True, on_delete=models.SET_NULL)

    panels = [
        FieldPanel('name'),
        FieldPanel('region'),
    ]

    class Meta:
        verbose_name_plural = 'Countries'
        ordering = ('name',)

    def save(self, *args, **kwargs):
        # Automatically set slug on save, if not already set
        if not self.slug:
            self.slug = slugify(self.name)

        super().save(*args, **kwargs)

    def __str__(self):
        return self.name


@register_snippet
class Tag(models.Model):
    name = models.CharField(max_length=100, unique=True)

    panels = [FieldPanel('name')]

    class Meta:
        ordering = ('name',)

    def __str__(self):
        return self.name


@register_snippet
class IndustryTag(models.Model):
    name = models.CharField(max_length=100, unique=True)
    icon = models.ForeignKey(
        AltTextImage,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )

    panels = [FieldPanel('name'), ImageChooserPanel('icon')]

    class Meta:
        ordering = ('name',)

    def __str__(self):
        return self.name


class TimeStampedModel(models.Model):
    """Modified version of django_extensions.db.models.TimeStampedModel

    Unfortunately, because null=True needed to be added to create and
    modified fields, inheritance causes issues with field clash.

    """

    created = CreationDateTimeField('created', null=True)
    modified = ModificationDateTimeField('modified', null=True)

    def save(self, **kwargs):
        self.update_modified = kwargs.pop('update_modified', getattr(self, 'update_modified', True))
        super().save(**kwargs)

    class Meta:
        get_latest_by = 'modified'
        ordering = (
            '-modified',
            '-created',
        )
        abstract = True


# Content models


class CMSGenericPage(
    mixins.EnableTourMixin,
    mixins.AuthenticatedUserRequired,
    mixins.WagtailGA360Mixin,
    GA360Mixin,
    Page,
):
    """
    Generic page, freely inspired by Codered page
    """

    class Meta:
        abstract = True

    # Do not allow this page type to be created in wagtail admin
    is_creatable = False
    template_choices = []

    ###############
    # Layout fields
    ###############
    template = models.CharField(
        max_length=255,
        choices=None,
    )

    #########
    # Panels
    ##########
    layout_panels = [FieldPanel('template')]
    settings_panels = [FieldPanel('slug')] + Page.settings_panels

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        field = self._meta.get_field('template')
        field.choices = self.template_choices
        field.required = True

    @cached_classmethod
    def get_edit_handler(cls):  # NOQA N805
        panels = [
            ObjectList(cls.content_panels, heading='Content'),
            ObjectList(cls.layout_panels, heading='Layout'),
            ObjectList(cls.settings_panels, heading='Settings', classname='settings'),
        ]

        return TabbedInterface(panels).bind_to(model=cls)

    def get_template(self, request, *args, **kwargs):
        return self.template

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request)

        self.set_ga360_payload(
            page_id=self.id,
            business_unit=settings.GA360_BUSINESS_UNIT,
            site_section=str(self.url or '/').split('/')[1],
        )
        self.add_ga360_data_to_payload(request)
        context['ga360'] = self.ga360_payload

        provider = get_context_provider(request=request, page=self)
        if provider:
            context.update(provider.get_context_data(request=request, page=self))
        return context


class LandingPage(CMSGenericPage):
    parent_page_types = [
        'domestic.DomesticHomePage',  # TODO: once we've restructured, remove this permission
        'domestic.GreatDomesticHomePage',
    ]
    subpage_types = [
        'core.ListPage',
        'core.InterstitialPage',
        'domestic.DomesticDashboard',
    ]
    template_choices = (
        ('learn/landing_page.html', 'Learn'),
        ('core/generic_page.html', 'Generic'),
    )

    ################
    # Content fields
    ################
    description = RichTextField()
    button = StreamField([('button', core_blocks.ButtonBlock(icon='cog'))], null=True, blank=True)
    image = models.ForeignKey(
        get_image_model_string(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+'
    )

    body = StreamField(
        [
            ('section', core_blocks.SectionBlock()),
            ('title', core_blocks.TitleBlock()),
            ('text', blocks.RichTextBlock(icon='openquote', helptext='Add a textblock')),
            ('image', core_blocks.ImageBlock()),
        ],
        null=True,
        blank=True,
    )

    components = StreamField(
        [
            ('route', core_blocks.RouteSectionBlock()),
        ],
        null=True,
        blank=True,
    )

    #########
    # Panels
    #########
    content_panels = CMSGenericPage.content_panels + [
        FieldPanel('description'),
        StreamFieldPanel('button'),
        ImageChooserPanel('image'),
        StreamFieldPanel('components'),
        StreamFieldPanel('body'),
    ]


class InterstitialPage(CMSGenericPage):
    parent_page_types = ['core.LandingPage']
    template_choices = (('learn/interstitial.html', 'Learn'),)

    ################
    # Content fields
    ################
    button = StreamField([('button', core_blocks.ButtonBlock(icon='cog'))], null=True, blank=True)

    #########
    # Panels
    #########
    content_panels = CMSGenericPage.content_panels + [
        StreamFieldPanel('button'),
    ]


class ListPage(CMSGenericPage):
    parent_page_types = ['core.LandingPage']
    subpage_types = ['core.CuratedListPage']

    template_choices = (('learn/automated_list_page.html', 'Learn'),)

    record_read_progress = models.BooleanField(
        default=False,
        help_text='Should we record when a user views a page in this collection?',
    )

    class Meta:
        verbose_name = 'Automated list page'
        verbose_name_plural = 'Automated list pages'

    def get_context(self, request, *args, **kwargs):
        from core.helpers import get_high_level_completion_progress
        from domestic.helpers import get_lesson_completion_status

        context = super().get_context(request)

        if request.user.is_authenticated:
            completion_status = get_lesson_completion_status(request.user)
            context['high_level_completion_progress'] = get_high_level_completion_progress(
                completion_status=completion_status,
            )
        return context

    ################
    # Content fields
    ################
    description = RichTextField()
    button_label = models.CharField(max_length=100)

    #########
    # Panels
    #########
    settings_panels = CMSGenericPage.settings_panels + [FieldPanel('record_read_progress')]
    content_panels = CMSGenericPage.content_panels + [FieldPanel('description'), FieldPanel('button_label')]


class CuratedListPage(CMSGenericPage):
    parent_page_types = ['core.ListPage']
    subpage_types = [
        'core.TopicPage',
    ]
    template_choices = (('learn/curated_list_page.html', 'Learn'),)

    ################
    # Content fields
    ################
    heading = RichTextField()
    image = models.ForeignKey(
        get_image_model_string(), null=True, blank=True, on_delete=models.SET_NULL, related_name='+'
    )

    ########
    # Panels
    ########
    content_panels = CMSGenericPage.content_panels + [
        FieldPanel('heading'),
        ImageChooserPanel('image'),
    ]

    def get_topics(self, live=True) -> models.QuerySet:
        qs = TopicPage.objects.live().specific().descendant_of(self)
        if live:
            qs = qs.live()
        return qs

    @cached_property
    def count_topics(self):
        return self.get_topics().count()

    @cached_property
    def count_detail_pages(self):
        count = 0
        for topic in self.get_topics():
            count += DetailPage.objects.live().descendant_of(topic).count()
        return count

    def get_context(self, request, *args, **kwargs):
        from core.helpers import (
            get_high_level_completion_progress,
            get_module_completion_progress,
        )
        from domestic.helpers import get_lesson_completion_status

        context = super().get_context(request)
        # Give the template a simple way to link back to the parent
        # learning module (ListPage)
        context['parent_page_url'] = self.get_parent().url

        if request.user.is_authenticated:
            # get this once, so we don't waste the network call to get the data twice
            completion_status = get_lesson_completion_status(request.user)
            context['module_completion_progress'] = get_module_completion_progress(
                completion_status=completion_status,
                module_page=self,
            )
            context['high_level_completion_progress'] = get_high_level_completion_progress(
                completion_status=completion_status,
            )
        return context


def hero_singular_validation(value):
    if value and len(value) > 1:
        raise StreamBlockValidationError(
            non_block_errors=ValidationError('Only one image or video allowed in Hero section', code='invalid'),
        )


class TopicPage(mixins.AuthenticatedUserRequired, Page):
    """Structural page to allow for cleaner mapping of lessons (`DetailPage`s)
    to modules (`CuratedListPage`s).

    Not intented to be viewed by end users, so will redirect to the parent
    module if accessed.

    Also, for the above reason, mixins.WagtailGA360Mixin and GA360Mixin
    are not used."""

    parent_page_types = ['core.CuratedListPage']
    subpage_types = [
        'core.DetailPage',
        'core.LessonPlaceholderPage',
    ]

    # `title` comes from Page superclass and that's all we need here

    def _redirect_to_parent_module(self):
        return HttpResponseRedirect(self.get_parent().url)

    def serve_preview(self, request, mode_name='dummy'):
        # It doesn't matter what is passed as mode_name - we always redirect
        return self._redirect_to_parent_module()

    def serve(self, request):
        return self._redirect_to_parent_module()


class LessonPlaceholderPage(mixins.AuthenticatedUserRequired, Page):

    """Structural page to allow for configuring and representing very simple
    to modules (`CuratedListPage`s).

    Not intented to be viewed by end users, so will redirect to the parent
    module if accessed.

    Also, for the above reason, mixins.WagtailGA360Mixin and GA360Mixin
    are not used."""

    parent_page_types = ['core.TopicPage']
    subpage_types = []  # No child pages allowed for placeholders

    # `title` comes from Page superclass and that's all we need here

    def _redirect_to_parent_module(self):
        dest = CuratedListPage.objects.ancestor_of(self).first().url
        return HttpResponseRedirect(dest)

    def serve_preview(self, request, mode_name='dummy'):
        # It doesn't matter what is passed as mode_name - we always redirect
        return self._redirect_to_parent_module()

    def serve(self, request):
        return self._redirect_to_parent_module()


class DetailPage(CMSGenericPage):
    estimated_read_duration = models.DurationField(null=True, blank=True)
    parent_page_types = [
        'core.CuratedListPage',  # TEMPORARY: remove after topics refactor migration has run
        'core.TopicPage',
    ]
    template_choices = (('learn/detail_page.html', 'Learn'),)

    class Meta:
        verbose_name = 'Detail page'
        verbose_name_plural = 'Detail pages'

    ################
    # Content fields
    ################
    hero = StreamField(
        [
            ('Image', core_blocks.ImageBlock(template='core/includes/_hero_image.html')),
            ('Video', core_blocks.SimpleVideoBlock(template='core/includes/_hero_video.html')),
        ],
        null=True,
        blank=True,
        validators=[hero_singular_validation],
    )
    objective = StreamField(
        [
            (
                'paragraph',
                blocks.RichTextBlock(options={'class': 'objectives'}),
            ),
            ('ListItem', core_blocks.Item()),
        ]
    )
    body = StreamField(
        [
            (
                'paragraph',
                blocks.StructBlock(
                    [('paragraph', blocks.RichTextBlock())],
                    template='core/struct_paragraph_block.html',
                    icon='fa-font',
                ),
            ),
            (
                'video',
                blocks.StructBlock(
                    [('video', core_blocks.VideoBlock())],
                    template='core/struct_video_block.html',
                    icon='fa-play',
                ),
            ),
            ('case_study', core_blocks.CaseStudyStaticBlock(icon='fa-book')),
            (
                'Step',
                core_blocks.StepByStepBlock(icon='cog'),
            ),
            (
                'fictional_example',
                blocks.StructBlock(
                    [('fiction_body', blocks.RichTextBlock(icon='openquote'))],
                    template='learn/fictional_company_example.html',
                    icon='fa-commenting-o',
                ),
            ),
            (
                'ITA_Quote',
                core_blocks.ITAQuoteBlock(icon='fa-quote-left'),
            ),
            (
                'pros_cons',
                blocks.StructBlock(
                    [
                        (
                            'pros',
                            blocks.StreamBlock(
                                [
                                    (
                                        'item',
                                        core_blocks.Item(icon='fa-arrow-right'),
                                    )
                                ]
                            ),
                        ),
                        (
                            'cons',
                            blocks.StreamBlock(
                                [
                                    (
                                        'item',
                                        core_blocks.Item(icon='fa-arrow-right'),
                                    )
                                ]
                            ),
                        ),
                    ],
                    template='learn/pros_and_cons.html',
                    icon='fa-arrow-right',
                ),
            ),
            ('choose_do_not_choose', core_blocks.ChooseDoNotChooseBlock()),
            (
                'image',
                core_blocks.ImageBlock(
                    template='core/includes/_image_full_width.html',
                    help_text='Image displayed within a full-page-width block',
                ),
            ),
            (
                'video',
                core_blocks.SimpleVideoBlock(
                    template='core/includes/_video_full_width.html',
                    help_text='Video displayed within a full-page-width block',
                ),
            ),
        ]
    )
    recap = StreamField(
        [
            (
                'recap_item',
                blocks.StructBlock(
                    [
                        ('title', blocks.CharBlock(icon='fa-header')),
                        (
                            'item',
                            blocks.StreamBlock(
                                [
                                    (
                                        'item',
                                        core_blocks.Item(),
                                    )
                                ]
                            ),
                        ),
                    ],
                    template='learn/recap.html',
                    icon='fa-commenting-o',
                ),
            )
        ]
    )

    #########
    # Panels
    ##########
    content_panels = Page.content_panels + [
        StreamFieldPanel('hero'),
        StreamFieldPanel('objective'),
        StreamFieldPanel('body'),
        StreamFieldPanel('recap'),
    ]

    def handle_page_view(self, request):
        if request.user.is_authenticated:
            # checking if the page should record read progress
            # checking if the page is already marked as read
            list_page = (
                ListPage.objects.ancestor_of(self)
                .filter(record_read_progress=True)
                .exclude(page_views_list__sso_id=request.user.pk, page_views_list__page=self)
                .first()
            )
            if list_page:
                PageView.objects.get_or_create(
                    page=self,
                    list_page=list_page,
                    sso_id=request.user.pk,
                )

    def serve(self, request, *args, **kwargs):
        self.handle_page_view(request)
        return super().serve(request, **kwargs)

    @cached_property
    def topic_title(self):
        return self.get_parent().title

    @cached_property
    def module(self):
        """Gets the learning module this lesson belongs to"""
        return CuratedListPage.objects.live().specific().ancestor_of(self).first()

    @cached_property
    def _export_plan_url_map(self):
        """Return a lookup dictionary of URL Slugs->title for all the
        Export Plan sections we have."""
        return {url: values['title'] for url, values in EXPORTPLAN_URL_MAP.items()}

    def _get_backlink(self, request):
        """Try to extract a backlink (used for a link to the export plan) from the
        querystring on the request that brought us to this view.

        Only accepts backlinks that we KNOW are for the export plan, else ignore it."""

        backlink_path = request.GET.get(BACKLINK_QUERYSTRING_NAME, '')
        if backlink_path is not None:
            backlink_path = unquote(backlink_path)
            if len(backlink_path.split('/')) > 2 and (
                backlink_path.split('/')[3] in EXPORTPLAN_SLUGS and '://' not in backlink_path
            ):
                # The check for '://' will stop us accepting a backlink which
                # features a full URL as its OWN querystring param (eg a crafted attack
                # URL), but that's an acceptable limitation here and is very unlikely
                # to happen.
                return backlink_path

        return None  # safe default

    def _get_backlink_title(self, backlink_path):
        """For a given backlink, see if we can get a title that goes with it.
        For now, this is limited only to Export Plan pages/links.
        """
        # We have to re-arrange EXPORT_PLAN_SECTION_TITLES_URLS after import
        # because it features lazily-evaluated URLs that aren't ready when
        # models are imported

        if backlink_path and len(backlink_path.split('/')) > 3:
            _path = backlink_path.split('/')[3]
            return self._export_plan_url_map.get(_path)

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request)
        context['refresh_on_market_change'] = True
        # Prepare backlink to the export plan if we detect one and can validate it
        _backlink = self._get_backlink(request)
        if _backlink:
            context['backlink'] = _backlink
            context['backlink_title'] = self._get_backlink_title(_backlink)

        if isinstance(self.get_parent().specific, TopicPage):
            # In a conditional because a DetailPage currently MAY be used as
            # a child of another page type...
            page_topic_helper = PageTopicHelper(self)
            next_lesson = page_topic_helper.get_next_lesson()
            context['current_lesson'] = self
            context['current_module'] = page_topic_helper.module
            if page_topic_helper:
                topic_page = page_topic_helper.get_page_topic()
                if topic_page:
                    context['current_topic'] = topic_page
                    context['page_topic'] = topic_page.title

            if next_lesson:
                context['next_lesson'] = next_lesson
            else:
                next_module = self.module.get_next_sibling()
                if not next_module:
                    return context
                context['next_module'] = next_module.specific
                context['next_lesson'] = get_first_lesson(next_module)
        return context


class PageView(TimeStampedModel):
    page = models.ForeignKey(DetailPage, on_delete=models.CASCADE, related_name='page_views')
    list_page = models.ForeignKey(ListPage, on_delete=models.CASCADE, related_name='page_views_list')
    sso_id = models.TextField()

    class Meta:
        ordering = ['page__pk']
        unique_together = ['page', 'sso_id']


# TODO: deprecate and remove
class ContentModuleTag(TaggedItemBase):
    content_object = ParentalKey('core.ContentModule', on_delete=models.CASCADE, related_name='tagged_items')


# TODO: deprecate and remove
@register_snippet
class ContentModule(ClusterableModel):
    title = models.CharField(max_length=255)
    content = RichTextField()
    tags = TaggableManager(through=ContentModuleTag, blank=True)

    panels = [
        FieldPanel('title'),
        FieldPanel('content'),
        FieldPanel('tags'),
    ]

    def __str__(self):
        return self.title


class PersonalisationHSCodeTag(TagBase):
    """Custom tag for personalisation.
    Tag value will be a HS6, HS4 or HS2 code"""

    # free_tagging = False  # DISABLED until tag data only comes via data migration

    class Meta:
        verbose_name = 'HS Code tag for personalisation'
        verbose_name_plural = 'HS Code tags for personalisation'


class PersonalisationCountryTag(TagBase):
    """Custom tag for personalisation.
    Tag value will be an ISO-2 Country code ('DE')
    """

    free_tagging = False

    class Meta:
        verbose_name = 'Country tag for personalisation'
        verbose_name_plural = 'Country tags for personalisation'


class PersonalisationRegionTag(TagBase):
    """Custom tag for personalisation.
    Tag value will be a geographical string ('Europe')
    """

    free_tagging = False

    class Meta:
        verbose_name = 'Region tag for personalisation'
        verbose_name_plural = 'Region tags for personalisation'


class PersonalisationTradingBlocTag(TagBase):
    """Custom tag for personalisation.
    Tag value will be an Trading blocs
    """

    free_tagging = False

    class Meta:
        verbose_name = 'Trading bloc tag for personalisation'
        verbose_name_plural = 'Trading bloc tags for personalisation'


# If you're wondering what's going on here:
# https://docs.wagtail.io/en/stable/reference/pages/model_recipes.html#custom-tag-models


class HSCodeTaggedCaseStudy(ItemBase):
    tag = models.ForeignKey(
        PersonalisationHSCodeTag, related_name='hscode_tagged_case_studies', on_delete=models.CASCADE
    )
    content_object = ParentalKey(to='core.CaseStudy', on_delete=models.CASCADE, related_name='hs_code_tagged_items')


class CountryTaggedCaseStudy(ItemBase):
    tag = models.ForeignKey(
        PersonalisationCountryTag, related_name='country_tagged_case_studies', on_delete=models.CASCADE
    )
    content_object = ParentalKey(to='core.CaseStudy', on_delete=models.CASCADE, related_name='country_tagged_items')


class RegionTaggedCaseStudy(ItemBase):
    tag = models.ForeignKey(
        PersonalisationRegionTag, related_name='region_tagged_case_studies', on_delete=models.CASCADE
    )
    content_object = ParentalKey(to='core.CaseStudy', on_delete=models.CASCADE, related_name='region_tagged_items')


class TradingBlocTaggedCaseStudy(ItemBase):
    tag = models.ForeignKey(
        PersonalisationTradingBlocTag, related_name='trading_bloc_tagged_case_studies', on_delete=models.CASCADE
    )
    content_object = ParentalKey(
        to='core.CaseStudy', on_delete=models.CASCADE, related_name='trading_bloc_tagged_items'
    )


def _high_level_validation(value, error_messages):
    TEXT_BLOCK = 'text'  # noqa N806
    MEDIA_BLOCK = 'media'  # noqa N806
    QUOTE_BLOCK = 'quote'  # noqa N806

    # we need to be strict about presence and ordering of these nodes
    if [node.block_type for node in value if node.block_type != QUOTE_BLOCK] != [MEDIA_BLOCK, TEXT_BLOCK]:
        error_messages.append(
            (
                'This block must contain one Media section (with one or '
                'two items in it) and/or a Quote section, then one Text section following it.'
            )
        )

    return error_messages


def _low_level_validation(value, error_messages):
    # Check content of media node, which should be present here

    MEDIA_BLOCK = 'media'  # noqa N806
    VIDEO_BLOCK = 'video'  # noqa N806

    for node in value:
        if node.block_type == MEDIA_BLOCK:
            subnode_block_types = [subnode.block_type for subnode in node.value]
            if len(subnode_block_types) == 2:
                if set(subnode_block_types) == {VIDEO_BLOCK}:
                    # Two videos: not allowed
                    error_messages.append('Only one video may be used in a case study.')
                elif subnode_block_types[1] == VIDEO_BLOCK:
                    # implicitly, [0] must be an image
                    # video after image: not allowed
                    error_messages.append('The video must come before a still image.')

    return error_messages


def case_study_body_validation(value):
    """Ensure the case study has exactly both a media node and a text node
    and that the media node has the following content:
        * One image, only
        * One video, only
        * One video + One image
            * (video must comes first so that it is displayed first)
        * Two images
    """

    error_messages = []

    if value:
        error_messages = _high_level_validation(value, error_messages)
        error_messages = _low_level_validation(value, error_messages)

        if error_messages:
            raise StreamBlockValidationError(
                non_block_errors=ValidationError('; '.join(error_messages), code='invalid'),
            )


class MagnaPageChooserPanel(PageChooserPanel):
    show_label = False

    field_template = 'admin/wagtailadmin/edit_handlers/field_panel_field.html'

    def render_as_field(self):
        instance_obj = self.get_chosen_item()
        context = {
            'field': self.bound_field,
            self.object_type_name: instance_obj,
            'is_chosen': bool(instance_obj),  # DEPRECATED - passed to templates for backwards compatibility only
            # Added obj_type on base class method render_as_field
            'obj_type': instance_obj.specific.__class__.__name__ if instance_obj else None,
        }
        return mark_safe(render_to_string(self.field_template, context))


class CaseStudyRelatedPages(Orderable):
    case_study = ParentalKey(
        'core.CaseStudy',
        related_name='related_pages',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    page = models.ForeignKey(
        'wagtailcore.Page',
        on_delete=models.CASCADE,
        related_name='+',
    )
    panels = [
        MagnaPageChooserPanel('page', [DetailPage, CuratedListPage, TopicPage]),
    ]

    class Meta:
        unique_together = ['case_study', 'page']


@register_snippet
class CaseStudy(ClusterableModel):
    """Dedicated snippet for use as a case study. Supports personalised
    selection via its tags.

    The decision about the appropriate Case Study block to show will happen
    when the page attempts to render the relevant CaseStudyBlock.

    Note that this is rendered via Wagtail's ModelAdmin, so appears in the sidebar,
    but we have to keep it registered as a Snippet to be able to transfer it
    with Wagtail-Transfer
    """

    title = models.CharField(
        max_length=255,
        blank=False,
        verbose_name='Internal case study title',
    )

    # old name company_name
    summary_context = models.CharField(max_length=255, blank=False, default='How we did it')
    # old name summary
    lead_title = models.TextField(blank=False)  # Deliberately not rich-text / no formatting
    body = StreamField(
        [
            (
                'media',
                blocks.StreamBlock(
                    [
                        ('video', core_blocks.SimpleVideoBlock(template='core/includes/_case_study_video.html')),
                        ('image', core_blocks.ImageBlock()),
                    ],
                    min_num=1,
                    max_num=2,
                ),
            ),
            (
                'text',
                blocks.RichTextBlock(
                    features=RICHTEXT_FEATURES__MINIMAL,
                ),
            ),
            (
                'quote',
                core_blocks.CaseStudyQuoteBlock(),
            ),
        ],
        validators=[case_study_body_validation],
        help_text=(
            'This block must contain one Media section (with one or two items in it) '
            'and/or Quote sections, then one Text section.'
        ),
    )

    # We are keeping the personalisation-relevant tags in separate
    # fields to aid lookup and make the UX easier for editors
    hs_code_tags = ClusterTaggableManager(through='core.HSCodeTaggedCaseStudy', blank=True, verbose_name='HS-code tags')

    country_code_tags = ClusterTaggableManager(
        through='core.CountryTaggedCaseStudy', blank=True, verbose_name='Country tags'
    )
    region_code_tags = ClusterTaggableManager(
        through='core.RegionTaggedCaseStudy', blank=True, verbose_name='Region tags'
    )
    trading_bloc_code_tags = ClusterTaggableManager(
        through='core.TradingBlocTaggedCaseStudy', blank=True, verbose_name='Trading bloc tags'
    )

    created = CreationDateTimeField('created', null=True)
    modified = ModificationDateTimeField('modified', null=True)

    panels = [
        MultiFieldPanel(
            [
                FieldPanel('title'),
                FieldPanel('lead_title'),
                FieldPanel('summary_context'),
                StreamFieldPanel('body'),
            ],
            heading='Case Study content',
        ),
        MultiFieldPanel(
            [
                FieldPanel('hs_code_tags'),
                FieldPanel('country_code_tags'),
                FieldPanel('region_code_tags'),
                FieldPanel('trading_bloc_code_tags'),
            ],
            heading='Case Study tags for Personalisation',
        ),
        MultiFieldPanel(
            [
                InlinePanel('related_pages', label='Related pages'),
            ],
            heading='Related Lesson, Topic & Module, also used for Personalisation',
        ),
    ]

    def __str__(self):
        display_name = self.title if self.title else self.summary_context
        return f'{display_name}'

    def save(self, **kwargs):
        # When we create a new CS need to call create to obtain an ID for indexing
        self.update_modified = kwargs.pop('update_modified', getattr(self, 'update_modified', True))
        super().save(**kwargs)
        update_cs_index(self)

    def delete(self, **kwargs):
        delete_cs_index(self.id)
        super().delete(**kwargs)

    def get_cms_standalone_view_url(self):
        return reverse('cms_extras:case-study-view', args=[self.id])

    class Meta:
        verbose_name_plural = 'Case studies'
        get_latest_by = 'modified'
        ordering = (
            '-modified',
            '-created',
        )


@register_setting
class CaseStudyScoringSettings(BaseSetting):
    threshold = models.DecimalField(
        help_text='This is the minimum score which a case study needs to have to be '
        'considered before being presented to users. ',
        default=10,
        decimal_places=3,
        max_digits=5,
    )
    lesson = models.DecimalField(
        help_text="Score given when user's lesson is tagged in the case study.",
        default=8,
        decimal_places=3,
        max_digits=5,
    )
    topic = models.DecimalField(
        help_text="Score given when user's lesson's topic is tagged in the case study "
        'unless there is also lesson match.',
        default=4,
        decimal_places=3,
        max_digits=5,
    )
    module = models.DecimalField(
        help_text="Score given when the user's lesson's module is tagged in the case study "
        'unless there is also lesson or topic match.',
        default=2,
        decimal_places=3,
        max_digits=5,
    )
    product_hs6 = models.DecimalField(
        help_text='Score given when any case study HS6 tag matches the complete HS6 code of '
        "any of the user's products",
        default=8,
        decimal_places=3,
        max_digits=5,
    )
    product_hs4 = models.DecimalField(
        help_text="Score given when any case study HS4 tag matches the first 4 digits of any of the user's products "
        'unless there is an HS6 match.',
        default=4,
        decimal_places=3,
        max_digits=5,
    )
    product_hs2 = models.DecimalField(
        help_text="Score given when any case study HS2 tag matches the first 2 digits of any of the user's products "
        'unless there is an HS6 or HS4 match.',
        default=2,
        decimal_places=3,
        max_digits=5,
    )
    country_exact = models.DecimalField(
        help_text="Score given when any case study country tag exactly matches one of the user's export markets.",
        default=4,
        decimal_places=3,
        max_digits=5,
    )
    country_region = models.DecimalField(
        help_text="Score given when any case study region tag matches the region of any of the user's export markets "
        'unless there is an exact country match.',
        default=2,
        decimal_places=3,
        max_digits=5,
    )

    trading_blocs = models.DecimalField(
        help_text='Score given when any case study trading bloc tag matches the any trading bloc that any of '
        "the user's export markets falls into unless there is an exact country or region match.",
        default=2,
        decimal_places=3,
        max_digits=5,
    )

    product_tab = [MultiFieldPanel([FieldPanel('product_hs6'), FieldPanel('product_hs4'), FieldPanel('product_hs2')])]
    market_tab = [
        MultiFieldPanel([FieldPanel('country_exact'), FieldPanel('country_region'), FieldPanel('trading_blocs')])
    ]
    lesson_tab = [MultiFieldPanel([FieldPanel('lesson'), FieldPanel('topic'), FieldPanel('module')])]

    threshold_tab = [
        MultiFieldPanel(
            [
                FieldPanel('threshold'),
            ]
        )
    ]

    edit_handler = TabbedInterface(
        [
            ObjectList(product_tab, heading='Product'),
            ObjectList(market_tab, heading='Market'),
            ObjectList(lesson_tab, heading='Lesson'),
            ObjectList(threshold_tab, heading='Threshold'),
        ]
    )

    class Meta:
        verbose_name = 'Case Study Scoring'

[(54, 'wagtail.snippets.models.register_snippet', 'register_snippet', False, 'from wagtail.snippets.models import register_snippet\n'), (105, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (117, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (123, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (127, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (133, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (145, 'django.db.models.OneToOneField', 'models.OneToOneField', False, 'from django.db import models\n'), (146, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (147, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (148, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (163, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (164, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (165, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (166, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (167, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (179, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (191, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (204, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (205, 'django.db.models.SlugField', 'models.SlugField', False, 'from django.db import models\n'), (206, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (230, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (243, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (244, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (269, 'django_extensions.db.fields.CreationDateTimeField', 'CreationDateTimeField', False, 'from django_extensions.db.fields import CreationDateTimeField, ModificationDateTimeField\n'), (270, 'django_extensions.db.fields.ModificationDateTimeField', 'ModificationDateTimeField', False, 'from django_extensions.db.fields import CreationDateTimeField, ModificationDateTimeField\n'), (309, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (374, 'wagtail.core.fields.RichTextField', 'RichTextField', False, 'from wagtail.core.fields import RichTextField, StreamField\n'), (434, 'django.db.models.BooleanField', 'models.BooleanField', False, 'from django.db import models\n'), (459, 'wagtail.core.fields.RichTextField', 'RichTextField', False, 'from wagtail.core.fields import RichTextField, StreamField\n'), (460, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (479, 'wagtail.core.fields.RichTextField', 'RichTextField', False, 'from wagtail.core.fields import RichTextField, StreamField\n'), (599, 'django.db.models.DurationField', 'models.DurationField', False, 'from django.db import models\n'), (852, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (853, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (854, 'django.db.models.TextField', 'models.TextField', False, 'from django.db import models\n'), (863, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (869, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (870, 'wagtail.core.fields.RichTextField', 'RichTextField', False, 'from wagtail.core.fields import RichTextField, StreamField\n'), (871, 'taggit.managers.TaggableManager', 'TaggableManager', False, 'from taggit.managers import TaggableManager\n'), (935, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (938, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (942, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (945, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (949, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (952, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (956, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (959, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (1042, 'modelcluster.models.ParentalKey', 'ParentalKey', False, 'from modelcluster.models import ClusterableModel, ParentalKey\n'), (1049, 'django.db.models.ForeignKey', 'models.ForeignKey', False, 'from django.db import models\n'), (1075, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (1082, 'django.db.models.CharField', 'models.CharField', False, 'from django.db import models\n'), (1084, 'django.db.models.TextField', 'models.TextField', False, 'from django.db import models\n'), (1118, 'modelcluster.contrib.taggit.ClusterTaggableManager', 'ClusterTaggableManager', False, 'from modelcluster.contrib.taggit import ClusterTaggableManager\n'), (1120, 'modelcluster.contrib.taggit.ClusterTaggableManager', 'ClusterTaggableManager', False, 'from modelcluster.contrib.taggit import ClusterTaggableManager\n'), (1123, 'modelcluster.contrib.taggit.ClusterTaggableManager', 'ClusterTaggableManager', False, 'from modelcluster.contrib.taggit import ClusterTaggableManager\n'), (1126, 'modelcluster.contrib.taggit.ClusterTaggableManager', 'ClusterTaggableManager', False, 'from modelcluster.contrib.taggit import ClusterTaggableManager\n'), (1130, 'django_extensions.db.fields.CreationDateTimeField', 'CreationDateTimeField', False, 'from django_extensions.db.fields import CreationDateTimeField, ModificationDateTimeField\n'), (1131, 'django_extensions.db.fields.ModificationDateTimeField', 'ModificationDateTimeField', False, 'from django_extensions.db.fields import CreationDateTimeField, ModificationDateTimeField\n'), (1188, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1195, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1201, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1208, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1215, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1222, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1229, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1236, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1242, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (1250, 'django.db.models.DecimalField', 'models.DecimalField', False, 'from django.db import models\n'), (109, 'hashlib.md5', 'hashlib.md5', False, 'import hashlib\n'), (151, 'wagtail.admin.edit_handlers.PageChooserPanel', 'PageChooserPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (152, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (153, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (154, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (170, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (171, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (172, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (173, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (182, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (193, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (209, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (210, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (232, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (252, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (252, 'wagtail.images.edit_handlers.ImageChooserPanel', 'ImageChooserPanel', False, 'from wagtail.images.edit_handlers import ImageChooserPanel\n'), (317, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (350, 'core.context.get_context_provider', 'get_context_provider', False, 'from core.context import get_context_provider\n'), (377, 'wagtail.images.get_image_model_string', 'get_image_model_string', False, 'from wagtail.images import get_image_model_string\n'), (481, 'wagtail.images.get_image_model_string', 'get_image_model_string', False, 'from wagtail.images import get_image_model_string\n'), (588, 'django.http.HttpResponseRedirect', 'HttpResponseRedirect', False, 'from django.http import HttpResponseRedirect\n'), (874, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (875, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (876, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1168, 'core.case_study_index.update_cs_index', 'update_cs_index', False, 'from core.case_study_index import delete_cs_index, update_cs_index\n'), (1171, 'core.case_study_index.delete_cs_index', 'delete_cs_index', False, 'from core.case_study_index import delete_cs_index, update_cs_index\n'), (1175, 'django.urls.reverse', 'reverse', False, 'from django.urls import reverse\n'), (60, 'django.utils.translation.ugettext_lazy', '_', True, 'from django.utils.translation import ugettext_lazy as _\n'), (64, 'django.utils.translation.ugettext_lazy', '_', True, 'from django.utils.translation import ugettext_lazy as _\n'), (220, 'django.utils.text.slugify', 'slugify', False, 'from django.utils.text import slugify\n'), (318, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (329, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (330, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (331, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (403, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (404, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (405, 'wagtail.images.edit_handlers.ImageChooserPanel', 'ImageChooserPanel', False, 'from wagtail.images.edit_handlers import ImageChooserPanel\n'), (406, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (407, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (424, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (450, 'domestic.helpers.get_lesson_completion_status', 'get_lesson_completion_status', False, 'from domestic.helpers import get_lesson_completion_status\n'), (451, 'core.helpers.get_high_level_completion_progress', 'get_high_level_completion_progress', False, 'from core.helpers import get_high_level_completion_progress, get_module_completion_progress\n'), (465, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (466, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (466, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (488, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (489, 'wagtail.images.edit_handlers.ImageChooserPanel', 'ImageChooserPanel', False, 'from wagtail.images.edit_handlers import ImageChooserPanel\n'), (523, 'domestic.helpers.get_lesson_completion_status', 'get_lesson_completion_status', False, 'from domestic.helpers import get_lesson_completion_status\n'), (524, 'core.helpers.get_module_completion_progress', 'get_module_completion_progress', False, 'from core.helpers import get_high_level_completion_progress, get_module_completion_progress\n'), (528, 'core.helpers.get_high_level_completion_progress', 'get_high_level_completion_progress', False, 'from core.helpers import get_high_level_completion_progress, get_module_completion_progress\n'), (744, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (745, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (746, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (747, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (794, 'urllib.parse.unquote', 'unquote', False, 'from urllib.parse import unquote\n'), (830, 'core.utils.PageTopicHelper', 'PageTopicHelper', False, 'from core.utils import PageTopicHelper, get_first_lesson\n'), (1038, 'django.template.loader.render_to_string', 'render_to_string', False, 'from django.template.loader import render_to_string\n'), (1274, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1275, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1276, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1277, 'wagtail.admin.edit_handlers.ObjectList', 'ObjectList', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (155, 'wagtail.admin.edit_handlers.InlinePanel', 'InlinePanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (334, 'wagtail.admin.edit_handlers.TabbedInterface', 'TabbedInterface', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (375, 'core.blocks.ButtonBlock', 'core_blocks.ButtonBlock', True, 'from core import blocks as core_blocks, mixins\n'), (382, 'core.blocks.SectionBlock', 'core_blocks.SectionBlock', True, 'from core import blocks as core_blocks, mixins\n'), (383, 'core.blocks.TitleBlock', 'core_blocks.TitleBlock', True, 'from core import blocks as core_blocks, mixins\n'), (384, 'wagtail.core.blocks.RichTextBlock', 'blocks.RichTextBlock', False, 'from wagtail.core import blocks\n'), (385, 'core.blocks.ImageBlock', 'core_blocks.ImageBlock', True, 'from core import blocks as core_blocks, mixins\n'), (393, 'core.blocks.RouteSectionBlock', 'core_blocks.RouteSectionBlock', True, 'from core import blocks as core_blocks, mixins\n'), (418, 'core.blocks.ButtonBlock', 'core_blocks.ButtonBlock', True, 'from core import blocks as core_blocks, mixins\n'), (537, 'django.core.exceptions.ValidationError', 'ValidationError', False, 'from django.core.exceptions import ValidationError\n'), (615, 'core.blocks.ImageBlock', 'core_blocks.ImageBlock', True, 'from core import blocks as core_blocks, mixins\n'), (616, 'core.blocks.SimpleVideoBlock', 'core_blocks.SimpleVideoBlock', True, 'from core import blocks as core_blocks, mixins\n'), (626, 'wagtail.core.blocks.RichTextBlock', 'blocks.RichTextBlock', False, 'from wagtail.core import blocks\n'), (628, 'core.blocks.Item', 'core_blocks.Item', True, 'from core import blocks as core_blocks, mixins\n'), (649, 'core.blocks.CaseStudyStaticBlock', 'core_blocks.CaseStudyStaticBlock', True, 'from core import blocks as core_blocks, mixins\n'), (652, 'core.blocks.StepByStepBlock', 'core_blocks.StepByStepBlock', True, 'from core import blocks as core_blocks, mixins\n'), (664, 'core.blocks.ITAQuoteBlock', 'core_blocks.ITAQuoteBlock', True, 'from core import blocks as core_blocks, mixins\n'), (697, 'core.blocks.ChooseDoNotChooseBlock', 'core_blocks.ChooseDoNotChooseBlock', True, 'from core import blocks as core_blocks, mixins\n'), (700, 'core.blocks.ImageBlock', 'core_blocks.ImageBlock', True, 'from core import blocks as core_blocks, mixins\n'), (707, 'core.blocks.SimpleVideoBlock', 'core_blocks.SimpleVideoBlock', True, 'from core import blocks as core_blocks, mixins\n'), (784, 'exportplan.core.data.SECTIONS.items', 'EXPORTPLAN_URL_MAP.items', True, 'from exportplan.core.data import SECTION_SLUGS as EXPORTPLAN_SLUGS, SECTIONS as EXPORTPLAN_URL_MAP\n'), (847, 'core.utils.get_first_lesson', 'get_first_lesson', False, 'from core.utils import PageTopicHelper, get_first_lesson\n'), (1100, 'wagtail.core.blocks.RichTextBlock', 'blocks.RichTextBlock', False, 'from wagtail.core import blocks\n'), (1106, 'core.blocks.CaseStudyQuoteBlock', 'core_blocks.CaseStudyQuoteBlock', True, 'from core import blocks as core_blocks, mixins\n'), (1136, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1137, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1138, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1139, 'wagtail.admin.edit_handlers.StreamFieldPanel', 'StreamFieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1145, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1146, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1147, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1148, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1154, 'wagtail.admin.edit_handlers.InlinePanel', 'InlinePanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1258, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1258, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1258, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1260, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1260, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1260, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1262, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1262, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1262, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (1267, 'wagtail.admin.edit_handlers.FieldPanel', 'FieldPanel', False, 'from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel, ObjectList, PageChooserPanel, StreamFieldPanel, TabbedInterface\n'), (94, 'django.urls.reverse', 'reverse', False, 'from django.urls import reverse\n'), (80, 'mimetypes.guess_type', 'mimetypes.guess_type', False, 'import mimetypes\n'), (636, 'wagtail.core.blocks.RichTextBlock', 'blocks.RichTextBlock', False, 'from wagtail.core import blocks\n'), (644, 'core.blocks.VideoBlock', 'core_blocks.VideoBlock', True, 'from core import blocks as core_blocks, mixins\n'), (657, 'wagtail.core.blocks.RichTextBlock', 'blocks.RichTextBlock', False, 'from wagtail.core import blocks\n'), (720, 'wagtail.core.blocks.CharBlock', 'blocks.CharBlock', False, 'from wagtail.core import blocks\n'), (1091, 'core.blocks.SimpleVideoBlock', 'core_blocks.SimpleVideoBlock', True, 'from core import blocks as core_blocks, mixins\n'), (1092, 'core.blocks.ImageBlock', 'core_blocks.ImageBlock', True, 'from core import blocks as core_blocks, mixins\n'), (676, 'core.blocks.Item', 'core_blocks.Item', True, 'from core import blocks as core_blocks, mixins\n'), (687, 'core.blocks.Item', 'core_blocks.Item', True, 'from core import blocks as core_blocks, mixins\n'), (727, 'core.blocks.Item', 'core_blocks.Item', True, 'from core import blocks as core_blocks, mixins\n')]