from .utils import obj2dict
from AccessControl import getSecurityManager
from Acquisition import aq_inner
from collections import OrderedDict
from datetime import datetime
from datetime import timezone
from json import dumps
from plone import api
from plone.event.utils import pydt
from plone.memoize import forever
from plone.memoize.view import memoize
from plone.memoize.view import memoize_contextless
from plone.namedfile.file import NamedBlobImage
from ploneintranet.calendar.content import ICentralCalendar
from ploneintranet.core import _
from ploneintranet.layout.browser.actions import PIDeleteConfirmationForm
from ploneintranet.layout.browser.base import BasePanel
from ploneintranet.layout.browser.base import BaseView
from ploneintranet.layout.interfaces import IDiazoAppTemplate
from ploneintranet.layout.pinning import IPinningBehavior
from ploneintranet.layout.utils import shorten
from ploneintranet.news.behaviors import INewsMeta
from ploneintranet.news.behaviors import INewsSyndication
from ploneintranet.news.browser.interfaces import IPublisherView
from ploneintranet.news.content import INewsApp
from ploneintranet.news.content import NEWS_SECTION_TYPES
from ploneintranet.news.interfaces import IBannerWithHero
from ploneintranet.news.interfaces import IBannerWithIcon
from ploneintranet.news.interfaces import ISplashPage
from ploneintranet.workspace.basecontent import baseviews
from ploneintranet.workspace.config import INTRANET_USERS_GROUP_ID
from Products.Five.browser import BrowserView
from z3c.form import button
from zope.deprecation import deprecate
from zope.interface import implementer
from zope.schema.vocabulary import getVocabularyRegistry

import feedparser
import logging

log = logging.getLogger(__name__)


@implementer(IDiazoAppTemplate, IPublisherView)
class NewsPublisher(BrowserView):
    title = _("label_title_app_news", default="News")
    section_class = "section-apps"
    feed_url_title_map = {
        "@@trending": _("Trending news"),
        "@@mygroups": _("News from my groups"),
    }

    @property
    @memoize
    def unpublished_states(self):
        """If the news state is unpublished, mark the news with a badge
        in the sidebar.
        """
        return list(
            api.portal.get_registry_record(
                "ploneintranet.news.unpublished_states", default=["private", "pending"]
            )
        )

    @property
    @memoize
    def published_states(self):
        """If the news state is unpublished, mark the news with a badge
        in the sidebar.
        """
        pc = api.portal.get_tool("portal_catalog")
        all_states = set(pc.uniqueValuesFor("review_state"))
        return list(all_states - set(self.unpublished_states))

    @property
    def app_url(self):
        return f"{self.context.absolute_url()}/@@publisher"

    def _num_items(self, grid, num_rows):
        """Calculate the number of items to return, based on the number of
        rows and the grid layout selected.
        We deliberately ignore that thinner columns (and thinner browser
        windows) may not be able to display the selected layout, but we still
        calculate the number of items as if it were.
        """
        num_rows = int(num_rows)
        if self.context.grid == "tc" or not grid or grid == "1":
            return num_rows
        elif grid == "1-3":
            return 1 + ((num_rows - 1) * 3)
        elif grid == "2-3":
            return 2 + ((num_rows - 1) * 3)
        elif grid == "3":
            return 3 * num_rows
        elif grid == "2":
            return 2 * num_rows
        else:
            return num_rows

    def _parse_column_fields(self):
        """Parse the form fields and extract the configuration for the magazine
        columns:
        {'1': OrderedDict([(  # Column number
            'bc88515aa59d41f0a43705968c13dd72', {   #Section UID
                'grid': '1-3',
                'num_rows': '2',
                'show_border': False,
                'show_descriptions': False,
                'show_title': False
            })]),
         }
        The values are derived from the field names, some are sent as a list,
        others are denoted by the start of the field name.
        """
        form = self.request.form
        columns = {"1": OrderedDict(), "2": OrderedDict(), "3": OrderedDict()}
        for field in form.get("tiles_order", []):
            __, ___, column_id, uid = field.split("-")
            if not form.get(f"col-{column_id}-{uid}", False):
                continue
            grid = form.get(f"grid-col-{column_id}-{uid}", "")
            num_rows = form.get(f"rows-col-{column_id}-{uid}", "2")
            columns[column_id][uid] = {
                "show_title": uid in form.get(f"title-col-{column_id}", []),
                "show_border": uid in form.get(f"border-col-{column_id}", []),
                "show_descriptions": uid
                in form.get(f"descriptions-col-{column_id}", []),
                "grid": grid,
                "num_rows": num_rows,
                "num_items": self._num_items(grid, num_rows),
            }

        return columns

    @memoize
    def get_target_sections(self):
        sections = self.context.listFolderContents({"portal_type": NEWS_SECTION_TYPES})
        sm = getSecurityManager()
        sections = (
            section
            for section in sections
            if (
                # Exclude smart and syndicated sections
                not self.is_smart_section(section)
                and not self.is_syndicated(section)
                and sm.checkPermission("Add portal content", section)
            )
        )
        return sorted(sections, key=lambda section: section.title)

    @memoize
    def is_syndicated(self, section):
        return section.portal_type == "ploneintranet.news.syndication"

    @memoize
    def is_smart_section(self, section):
        if getattr(section, "view_name", None):
            return True
        if not self.is_syndicated(section):
            return False
        if section.feed_url:
            return section.feed_url.startswith("@@")

    def __call__(self):
        context = self.context
        get = self.request.form.get
        if self.request.method == "POST":
            # Creating a new item
            if get("form.id") == "create":
                # Its a section
                if get("type") == "section":
                    self.create_section()
                # Or an item
                elif get("type") == "item":
                    self.create_item()
                elif get("type") == "syndication":
                    self.create_syndication()
                else:
                    pass  # unsupported type

            # Editing frontpage config
            elif get("form.id") == "frontpage":
                context.grid = get("grid")
                context.columns = self._parse_column_fields()
                if get("button_save"):
                    return self.index()
                elif get("tiles_order"):
                    return self.index()
            else:
                pass  # unsupported state
                self.request.response.redirect(context.absolute_url() + "/@@publisher")

        return super().__call__()

    @memoize
    def sections(self):
        _sections = []
        for section in self.get_target_sections():
            news_items = self.news_items(section)
            delete_protected = len(news_items) > 0
            has_more = len(news_items) == 5
            _sections.append(
                obj2dict(
                    section,
                    "id",
                    "title",
                    "description",
                    "absolute_url",
                    news_items=news_items,
                    delete_protected=delete_protected,
                    has_more=has_more,
                )
            )
        if len(_sections) == 1:
            _sections[0]["delete_protected"] = True
        return _sections

    @memoize
    def news_items(self, section=None, start=None, limit=5):
        desc_len = 160
        _items = []
        unpublished_states = self.unpublished_states

        result = self.context.news_items(
            section, start, limit, sort_on="created", sort_order="reverse"
        )

        i = 0
        for brain in result:
            i += 1
            item = brain.getObject()
            if item.section:
                category = item.section.to_object.title
            else:
                category = None
            published = api.content.get_state(item) not in unpublished_states
            _items.append(
                obj2dict(
                    item,
                    "id",
                    "title",
                    "absolute_url",
                    description=shorten(item.description, desc_len),
                    date=item.effective().strftime("%B %d, %Y"),
                    category=category,
                    counter=i,
                    can_edit=api.user.has_permission("Modify", obj=item),
                    published=published,
                )
            )
        return _items

    def _create_section(self, title, **kwargs):
        get = self.request.form.get
        if "description" in kwargs:
            description = kwargs.pop("description")
        else:
            description = get("section_description", "")
        description_visible = bool(get("section-description-visible", False))
        navigation = get("navigation", "exclude")
        nav_visible_group_list = set(
            filter(None, get("nav_visible_group_list", "").split(","))
        )
        grid = get("grid", "")
        color = get("color", "")
        maintainer_group_list = set(
            filter(None, get("maintainer_group_list", "").split(","))
        )
        contribution = get("contribution", "none")
        if contribution == "groups":
            contributor_group_list = set(
                filter(None, get("contributor_group_list", "").split(","))
            )
        elif contribution == "all":
            contributor_group_list = {INTRANET_USERS_GROUP_ID}
        else:
            contributor_group_list = set()
        show_descriptions = True if get("show_descriptions") else False
        show_images = True if get("show_images") else False
        return api.content.create(
            container=self.context,
            title=title,
            description=description,
            description_visible=description_visible,
            navigation=navigation,
            nav_visible_group_list=nav_visible_group_list,
            maintainer_group_list=maintainer_group_list,
            contributor_group_list=contributor_group_list,
            grid=grid,
            color=color,
            show_descriptions=show_descriptions,
            show_images=show_images,
            **kwargs,
        )

    def create_section(self):
        get = self.request.form.get
        title = get("title", "")
        view_name = self.request.get("view_name", "")
        calendar = self.request.get("calendar", "")
        if not title and not view_name:
            log.error("No title given for section creation")
            return
        self._create_section(
            type="ploneintranet.news.section",
            title=title,
            view_name=view_name,
            calendar=calendar,
        )

    def create_syndication(self):
        """Given an atom or an RSS feed,
        create a section with the data from the feed
        """
        get = self.request.form.get
        feed_url = get("feed_url", "")
        title = get("title", "")
        description = get("section_description", "")
        if not title:
            feed_data = feedparser.parse(feed_url)
            feed = feed_data.feed
            title = feed.title or feed_url
            if not description:
                description = feed.subtitle
        section = self._create_section(
            type="ploneintranet.news.syndication",
            title=title,
            description=description,
            feed_url=feed_url,
        )
        syndication = INewsSyndication(section)
        syndication.sync_news()

    def create_item(self):
        get = self.request.form.get
        title = get("title", "")
        if not title:
            log.error("No title given for item creation")
            return
        description = get("item_description", "")
        section = self.context.get(get("section", ""), "")
        item = api.content.create(
            container=section,
            type="News Item",
            title=title,
            description=description,
        )
        INewsMeta(item).magazine_home = True
        item.indexObject()
        # Store in UTC, render as local time.
        item.setEffectiveDate(datetime.now(timezone.utc))
        log.info(f"Created news item {item}")
        return self.request.response.redirect(item.absolute_url() + "/@@edit.html")


class BaseEditPanel(BasePanel):
    @property
    def colors(self):
        return {
            "AliceBlue": "Alice blue",
            "Beige": "Beige",
            "BurlyWood": "Burly wood",
            "DarkSalmon": "Dark salmon",
            "DarkSeaGreen": "Dark sea green",
            "Khaki": "Khaki",
            "Lavender": "Lavender",
            "LavenderBlush": "Lavender blush",
            "LightBlue": "Light blue",
            "LightCyan": "Light cyan",
            "LightGoldenRodYellow": "Light golden rod yellow",
            "LightGreen": "Light green",
            "LightPink": "Light pink",
            "LightSalmon": "Light salmon",
            "LightSteelBlue": "Light steel blue",
            "MistyRose": "Misty rose",
            "OldLace": "Old lace",
            "PeachPuff": "Peach puff",
            "Thistle": "Thistle",
            "Wheat": "Wheat",
        }

    def central_calendars(self):
        return api.portal.get_tool("portal_catalog")(
            object_provides=ICentralCalendar.__identifier__
        )


class AddPanel(BaseEditPanel):
    """The panel that adds news or news sections"""

    _form_data_pat_inject_parts = (
        "#global-statusmessage; loading-class: ''",
        "#application-body",
    )
    title = _("Create news item or section")
    panel_size = "medium"
    panel_style = "panel"
    panel_instance = "panel-create-news"

    @property
    @memoize
    def tabs(self):
        publisher_view = api.content.get_view("publisher", self.context, self.request)
        tabs = []
        if publisher_view.get_target_sections():
            tabs.append(
                {
                    "css_class": "icon-newspaper current",
                    "id": "sheet-article",
                    "label": _("label_news_item", "News item"),
                }
            )
        if api.user.has_permission("Add portal content", obj=self.context):
            tabs.extend(
                (
                    {
                        "css_class": "icon-squares",
                        "id": "sheet-section",
                        "label": _("label_news_section", "Section"),
                    },
                    {
                        "css_class": "icon-brain",
                        "id": "sheet-smartsection",
                        "label": _("Smart section"),
                    },
                    {
                        "css_class": "icon-rss",
                        "id": "sheet-syndication",
                        "label": _("Syndication"),
                    },
                )
            )
        return tabs

    @property
    @memoize
    def form_action(self):
        """This form posts to the publisher"""
        return "{url}/@@{action}".format(
            url=self.context.absolute_url(), action="publisher"
        )


class SectionLoadMore(BrowserView):
    @property
    @memoize_contextless
    def app(self):
        for obj in self.context.aq_chain:
            if INewsApp.providedBy(obj):
                return obj

    @property
    @memoize_contextless
    def utcnow(self):
        return datetime.now(timezone.utc)

    @property
    @memoize_contextless
    def publisher_view(self):
        """Return the publisher view in the context of the app"""
        return api.content.get_view("publisher", self.app, self.request)

    def is_unpublished(self, brain):
        """Is a News Item brain invisible because of review state?"""
        return brain.review_state in self.publisher_view.unpublished_states

    def is_scheduled(self, brain):
        """Is a published News Item brain invisible because of publication date?"""
        return (
            brain.review_state in self.publisher_view.published_states
            and pydt(brain.context["effective"]) > self.utcnow
        )

    def is_expired(self, brain):
        """Is a published News Item brain invisible because of expiry?"""
        return (
            brain.review_state in self.publisher_view.published_states
            and pydt(brain.context["expires"]) < self.utcnow
        )

    @memoize
    def news_items(self):
        """Get the news items from the current section"""
        return self.app.news_items(
            section=self.context,
            start=self.b_start,
            limit=self.b_size,
            sort=["-effective"],
        )

    # Batching support
    @property
    def view_name(self):
        return self.__name__

    @property
    def b_size(self):
        return int(self.request.get("b_size", 10))

    @property
    def b_start(self):
        return int(self.request.get("b_start", 0))

    @property
    def has_more(self) -> bool:
        """Check if the query has more results after the current batch"""
        news_items = self.news_items()
        try:
            count = news_items.total_results
        except AttributeError:
            count = len(news_items)
        return count > (self.b_start + self.b_size)

    @deprecate("Deprecated in 6.3.2.dev0. Use news_items instead.")
    def batch(self):
        return self.news_items()


class SectionEdit(BaseEditPanel):
    title = _("Section settings")

    _form_data_pat_inject_parts = (
        "#global-statusmessage; loading-class: ''",
        "#sidebar-content",
    )

    @property
    def url(self):
        return self.request.get("ACTUAL_URL")

    @property
    def app_url(self):
        return f"{self.app.absolute_url()}/@@publisher"

    @property
    @memoize_contextless
    def app(self):
        for obj in self.context.aq_chain:
            if INewsApp.providedBy(obj):
                return obj

    def __call__(self):
        if self.request.method == "POST":
            self.update()
            return self.request.response.redirect(self.app_url)
        else:
            return super().__call__()

    @property
    @memoize_contextless
    def publisher(self):
        return api.content.get_view("publisher", self.app, self.request)

    def update(self):
        context = self.context
        get = self.request.form.get
        title = get("title", "")
        if not title:
            log.error("No title given for section update")
            return
        context.title = title
        context.description = get("description", "")
        context.section_visible = bool(get("section-visible", False))
        context.description_visible = bool(get("section-description-visible", False))
        context.grid = get("grid", "")
        context.color = get("color", "")
        context.navigation = get("navigation", "main")
        if get("visibility", "all") == "groups":
            context.nav_visible_group_list = set(
                filter(None, get("nav_visible_group_list", "").split(","))
            )
        else:
            context.nav_visible_group_list = set()
        context.show_descriptions = True if get("show_descriptions") else False
        context.show_images = True if get("show_images") else False
        if get("maintenance", "none") == "groups":
            context.maintainer_group_list = set(
                filter(None, get("maintainer_group_list", "").split(","))
            )
        else:
            context.maintainer_group_list = set()
        contribution = get("contribution", "none")
        if contribution == "groups":
            context.contributor_group_list = set(
                filter(None, get("contributor_group_list", "").split(","))
            )
        elif contribution == "all":
            context.contributor_group_list = {INTRANET_USERS_GROUP_ID}
        else:
            context.contributor_group_list = set()
        context.view_name = get("view_name", "")
        context.calendar = get("calendar", "")
        if self.context.portal_type == "ploneintranet.news.syndication":
            news_from_feed_creator = get(
                "news_from_feed_creator", context.news_from_feed_creator
            )
            if news_from_feed_creator != context.news_from_feed_creator:
                context.news_from_feed_creator = news_from_feed_creator
            feed_url = get("feed_url", "")
            if feed_url != context.feed_url:
                context.feed_url = feed_url
                syndication = INewsSyndication(context)
                syndication.sync_news()


class SectionDelete(SectionEdit):
    title = ""
    panel_size = "small"

    def update(self):
        log.info(f"Deleting {self.context}")
        api.content.delete(obj=self.context, check_linkintegrity=False)


@implementer(IPublisherView)
class NewsItemEdit(baseviews.ContentView):
    _manage_splashpage_permission = "Manage portal"
    _review_permission = "Review portal content"
    banner_with_hero_iface = IBannerWithHero
    banner_with_icon_iface = IBannerWithIcon
    banner_with_icon_identifier = IBannerWithIcon.__identifier__
    splash_page_iface = ISplashPage
    type_class = "news-item"

    @property
    @memoize
    def show_image_gallery(self):
        return api.portal.get_registry_record(
            "ploneintranet.news.show_image_gallery", default=True
        )

    @property
    @memoize
    def app(self):
        for obj in self.context.aq_chain:
            if INewsApp.providedBy(obj):
                return obj

    def app_url(self):
        return self.app.absolute_url()

    @property
    @memoize
    def carousel_slides(self):
        """Return a list of images for the carousel"""
        objs = (relation.to_object for relation in self.context.carousel_slides)
        return [obj for obj in objs if obj is not None and obj.portal_type == "Image"]

    @property
    @memoize
    def is_banner_with_icon(self):
        """Check if this news can go in a banner with an icon"""
        return self.banner_with_icon_iface.providedBy(self.context)

    @property
    @memoize
    def is_banner_with_hero(self):
        """Check if this news can go in a banner with an hero image"""
        return self.banner_with_hero_iface.providedBy(self.context)

    @property
    @memoize
    def is_splash_page(self):
        """Check if this news is a splashpage"""
        return self.splash_page_iface.providedBy(self.context)

    @property
    @memoize
    def can_manage_splashpages(self):
        return False  # XXX temporarily disabled
        return self.has_permission(self._manage_splashpage_permission, self.context)

    @property
    @memoize
    def can_review(self):
        return self.has_permission(self._review_permission, aq_inner(self.context))

    @property
    @forever.memoize
    def banner_options(self):
        """Return the options for the banner vocabulary."""
        is_banner_with_icon = self.is_banner_with_icon
        is_banner_with_hero = self.is_banner_with_hero
        options = (
            {
                "title": _("No"),
                "value": "",
                "selected": not is_banner_with_icon and "selected" or None,
            },
            {
                "title": _("Yes, with an icon"),
                "value": self.banner_with_icon_iface.__identifier__,
                "selected": is_banner_with_icon and "selected" or None,
            },
            {
                "title": _("Yes, with hero image"),
                "value": self.banner_with_hero_iface.__identifier__,
                "selected": is_banner_with_hero and "selected" or None,
            },
        )
        return options

    @property
    @forever.memoize
    def banner_icons(self):
        """Return the ploneintranet.layout.icons vocabulary"""
        vr = getVocabularyRegistry()
        return vr.get(self.context, "ploneintranet.layout.icons")

    @property
    @memoize
    def hero_image_aspect_ratio_options(self):
        """Return the aspect ratio options vocabulary.

        We conceptually have a matrix of 2x2 choices:
        sitewide: cropped | uncropped
        context:  cropped | uncropped

        The sitewide choice is active, unless a local override is saved on the context.
        """
        stored_value = self.context.hero_image_aspect_ratio
        default_value = api.portal.get_registry_record(
            "ploneintranet.news.hero_default_aspect_ratio", default="cropped"
        )
        if default_value == "cropped":
            default_title = _(
                "label_news_hero_default_cropped", default="Site default (cropped)"
            )
        else:
            default_title = _(
                "label_news_hero_default_uncropped", default="Site default (uncropped)"
            )
        options = [
            {
                "title": default_title,
                "value": "--NOVALUE--",
                "selected": stored_value is None,
            }
        ]
        vr = getVocabularyRegistry()
        vocabulary = vr.get(
            self.context, "ploneintranet.news.vocabulary.hero_image_aspect_ratio"
        )
        options.extend(
            [
                {
                    "title": option.title,
                    "value": option.value,
                    "selected": option.value == stored_value,
                }
                for option in vocabulary
            ]
        )
        return options

    @property
    @forever.memoize
    def carousel_show_as_options(self):
        """Return the options for the carousel_show_as vocabulary"""
        vr = getVocabularyRegistry()
        return vr.get(self.context, "ploneintranet.news.vocabulary.carousel_show_as")

    @property
    @forever.memoize
    def carousel_height_options(self):
        """Return the options for the carousel_height vocabulary"""
        vr = getVocabularyRegistry()
        return vr.get(self.context, "ploneintranet.news.vocabulary.carousel_height")

    @property
    @forever.memoize
    def carousel_position_options(self):
        """Return the options for the carousel_position vocabulary"""
        vr = getVocabularyRegistry()
        return vr.get(self.context, "ploneintranet.news.vocabulary.carousel_position")

    def sections(self):
        _sections = []
        for section in self.app.sections():
            context_section = self.context.aq_parent
            current = section == context_section
            _sections.append(
                obj2dict(section, "title", current=current, uuid=section.uuid)
            )
        return _sections

    @property
    def magazine_home(self):
        return INewsMeta(self.context).magazine_home

    @property
    def pinned(self):
        return IPinningBehavior(self.context).pinned

    @property
    @memoize_contextless
    def userprofiles(self):
        """Return the userprofile container view"""
        return api.content.get_view("view", api.portal.get().profiles, self.request)

    def update(self):
        context = self.context
        form = self.request.form
        # do not remove existing image without new upload
        if "image" in list(form.keys()) and not bool(form.get("image")):
            del form["image"]

        default_view = super().update()
        new_section_uid = form.get("section")
        old_section_uid = context.aq_parent.UID()
        if not new_section_uid or new_section_uid == old_section_uid:
            return default_view
        else:
            new_section = api.content.get(UID=new_section_uid)
            moved_item = api.content.move(source=self.context, target=new_section)
            self.request.response.redirect(moved_item.absolute_url() + "/edit.html")

    @property
    def effective_date(self):
        return self.format_date(self.context.effective())

    @property
    def effective_time(self):
        return self.format_time(self.context.effective())

    @property
    def expiry_date(self):
        return self.format_date(self.context.expires())

    @property
    def expiry_time(self):
        return self.format_time(self.context.expires())

    def format_date(self, dt):
        date = dt.strftime("%Y-%m-%d")
        if date.startswith("20"):
            return date
        else:
            return ""

    def format_time(self, dt):
        # Note that this renders in the OS configured tzdata,
        # not in the Plone configured timezone.
        return dt.strftime("%H:%M")

    def member_prefill(self, context, field, default=None):
        """
        Return JSON for pre-filling a pat-autosubmit field with the values for
        that field
        """
        field_value = getattr(context, field, default)
        if not field_value:
            return ""
        if isinstance(field_value, str):
            assigned_users = field_value.split(",")
        else:
            assigned_users = field_value
        prefill = {}
        for user_id in assigned_users:
            user = api.user.get(user_id)
            if user:
                prefill[user_id] = (
                    user.getProperty("fullname") or user.getId() or user_id
                )
        return dumps(prefill)


class NewsItemDelete(PIDeleteConfirmationForm):
    """Override the Delete confirmation view to redirect to the publisher

    Note: we need to override both the actions
    """

    @property
    @memoize
    def app_url(self):
        for obj in self.context.aq_chain:
            if INewsApp.providedBy(obj):
                return f"{obj.absolute_url()}/@@publisher"
        return self.context.aq_parent.absolute_url()

    @button.buttonAndHandler(_("I am sure, delete now"), name="Delete")
    def handle_delete(self, action):
        base_handler = super().handle_delete
        base_handler(self, action)
        return self.request.response.redirect(self.app_url)

    @button.buttonAndHandler(_("label_cancel", default="Cancel"), name="Cancel")
    def handle_cancel(self, action):
        return self.request.response.redirect(self.view_url())


class PanelEditFrontPage(BrowserView):
    def __call__(self):
        self.columns = self.context.columns if self.context.columns else {}
        return self.index()

    def checked(self, field, column_id, uid, default="", value=""):
        if not self.context.columns:
            return default
        column = self.context.columns.get(column_id)
        if column:
            section = column.get(uid)
            if section:
                field_value = section.get(field, False)
                if field == "assigned":
                    return "checked"
                elif field == "grid":
                    return "checked" if field_value == value else default
                else:
                    return "checked" if field_value else default
        return default

    @property
    @memoize
    def sections(self):
        return self.context.listFolderContents(
            contentFilter={"portal_type": NEWS_SECTION_TYPES}
        )

    def sections_ordered(self, column_id):
        # Return the sections in the order of
        # - available first as they are placed in the news folder
        # - then the sections with checkbox on in the order as manually sorted.
        ordered_sections = []
        all_sections = self.sections
        column_items = self.columns.get(str(column_id), {})
        added_uids = list(column_items.keys())
        sections_by_uid = self.sections_by_uid()

        for uid in added_uids:
            sections = sections_by_uid.get(uid)
            if sections:
                ordered_sections.append(sections)
        for section in all_sections:
            if section.uuid not in added_uids:
                ordered_sections.append(section)
        return ordered_sections

    @memoize
    def sections_by_uid(self):
        sections = OrderedDict()
        for section in self.sections:
            sections[section.uuid] = section

        return sections

    def get_section_by_uid(self, uid):
        return self.sections_by_uid()[uid]

    def items_in_column(self, column_id):
        column_items = self.columns.get(str(column_id), [])
        return len(column_items) != 0


class UploadSlideImages(BaseView):
    def __call__(self):
        carousel_slides = self.request.form.get("new_carousel_slides", [])
        for carousel_slide in carousel_slides:
            obj = api.content.create(
                container=self.context.aq_parent,
                type="Image",
                title=carousel_slide.filename,
                image=NamedBlobImage(
                    data=carousel_slide,
                    filename=carousel_slide.filename,
                ),
            )
            api.relation.create(self.context, obj, "carousel_slides")
        return self.redirect(f"{self.context.absolute_url()}/@@edit.html")


class RemoveSlideImage(BaseView):
    def __call__(self):
        if self.request.method == "POST":
            bad_uid = self.request.form.get("carousel_slides", None)
            good_relations = []
            for relation in self.context.carousel_slides:
                obj = relation.to_object
                if obj and obj.UID() != bad_uid:
                    good_relations.append(relation)
            self.context.carousel_slides = good_relations
        return self.redirect(f"{self.context.absolute_url()}/@@edit.html")


class EditSlideDescription(BaseView):
    def __call__(self):
        if self.request.method == "POST":
            if self.request.form.get("carousel_slides") == self.context.UID():
                self.context.description = self.request.form.get(
                    "carousel_slide_caption", ""
                )
        return self.redirect(f"{self.context.absolute_url()}/@@image_view")
