_APPS_MODEL = None
    _REQUIRED = {"key", "title", "template_name", "regions", "app_namespace"}
    _REQUIRED = {"key", "title", "urlconf", "app_namespace"}
    LANGUAGE_CODES_NAMESPACE = "apps"
            _APPS_MODEL = sender
def reverse_any(
    viewnames,
    urlconf=None,
    args=None,
    kwargs=None,
    fallback=_sentinel,
    *fargs,
    **fkwargs,
):
def reverse_app(namespaces, viewname, *args, languages=None, **kwargs):
def reverse_fallback(fallback, fn, *args, **kwargs):
def _del_apps_urlconf_cache(**kwargs):
def apps_urlconf(*, apps=None):
def page_for_app_request(request, *, queryset=None):
def apps_middleware(get_response):
    def middleware(request):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def type(self):
    def regions(self):
    def save(self, *args, **kwargs):
    def clean_fields(self, exclude=None):
    def fill_page_type_choices(sender, **kwargs):
    def check(cls, **kwargs):
    def _check_feincms3_appsmixin_templatemixin_clash(cls, **kwargs):
    def _check_feincms3_applications(cls, **kwargs):
class TemplateType(Type):
class ApplicationType(Type):
class PageTypeMixin(models.Model):
    class Meta: class MenuMixin(models.Model):
    class Meta:
class TemplateMixin(models.Model):
    class Meta:
class LanguageMixin(models.Model):
    class Meta:
class LanguageAndTranslationOfMixin(LanguageMixin):
    class Meta:
class RedirectMixin(models.Model):
    class Meta:
    def fill_menu_choices(sender, **kwargs):
    def check(cls, **kwargs):
    def _check_feincms3_menu_mixin(cls, **kwargs):
    def template(self):
    def regions(self):
    def fill_template_key_choices(sender, **kwargs):
    def activate_language(self, request):
    def check(cls, **kwargs):
    def _check_feincms3_language_and_translation_of_mixin(cls, **kwargs):
    def translations(self):
    def clean_fields(self, exclude=None):
    def get_redirect_url(self):
    def clean_fields(self, exclude=None): class ApplicationType(Type):
    """
    Application page type

    Example usage:

    .. code-block:: python

        from feincms3.applications import PageTypeMixin, TemplateType
        from feincms3.pages import AbstractPage
        from content_editor.models import Region

        class Page(AbstractPage, PageTypeMixin)
            TYPES = [
                TemplateType(
                    # Required arguments
                    key="standard",
                    title="Standard page",
                    urlconf="path.to.urlconf.module",

                    # Optional arguments
                    template_name="pages/standard.html",
                    regions=[
                        Region(key="main", title="Main"),
                    ],
                    app_namespace=lambda page: ...,

                    # You may pass other arguments here, they will be available
                    # on ``page.type`` as-is.
                ),
            ]
    """

    _REQUIRED = {"key", "title", "urlconf", "app_namespace"}

    def __init__(self, **kwargs):
        kwargs.setdefault("template_name", "")
        kwargs.setdefault("regions", [])
        kwargs.setdefault("app_namespace", lambda instance: instance.page_type)
        super().__init__(**kwargs) class PageTypeMixin(models.Model):
    """
    The page class should inherit this mixin. It adds a ``page_type`` field
    containing the selected page type, and an ``app_namespace`` field which
    contains the instance namespace of the application, if the type of the page
    is an application type. The field is empty e.g. for template page types.
    Note that currently the :class:`~feincms3.mixins.LanguageMixin` is a
    required dependency of :mod:`feincms3.applications`.

    ``TYPES`` contains a list of page type instances, either
    :class:`~feincms3.applications.TemplateType` or
    :class:`~feincms3.applications.ApplicationType` and maybe others in the
    future. The configuration values are specific to each type, common to all
    of them are a key (stored in the ``page_type`` field) and a user-visible
    title.

    Template types additionally require a ``template_name`` and a ``regions``
    value.

    Application types require a ``urlconf`` value and support the following
    options:

    - ``urlconf``: The path to the URLconf module for the application. Besides
      the ``urlpatterns`` list the module should probably also specify a
      ``app_name``.
    - ``required_fields``: A list of page class fields which must be non-empty
      for the application to work. The values are checked in
      ``PageTypeMixin.clean_fields``.
    - ``app_namespace``: A callable which receives the page instance
      as its only argument and returns a string suitable for use as an
      instance namespace.

    Usage::

        from content_editor.models import Region
        from django.utils.translation import gettext_lazy as _
        from feincms3.applications import PageTypeMixin
        from feincms3.mixins import LanguageMixin
        from feincms3.pages import AbstractPage

        class Page(AbstractPage, PageTypeMixin, LanguageMixin):
            TYPES = [
                # It is recommended to always put a TemplateType type first
                # because it will be the default type:
                TemplateType(
                    key="standard",
                    title=_("Standard"),
                    template_name="pages/standard.html",
                    regions=[Region(key="main", title=_("Main"))],
                ),
                ApplicationType(
                    key="publications",
                    title=_("publications"),
                    urlconf="app.articles.urls",
                ),
                ApplicationType(
                    key="blog",
                    title=_("blog"),
                    urlconf="app.articles.urls",
                ),
                ApplicationType(
                    key="contact",
                    title=_("contact form"),
                    urlconf="app.forms.contact_urls",
                ),
                ApplicationType(
                    key="teams",
                    title=_("teams"),
                    urlconf="app.teams.urls",
                    app_namespace=lambda page: f"{page.page_type}-{page.team_id}",
                    required_fields=["team"],
                ),
            ]
    """

    #: Override this to set a different name for the outer namespace.
    LANGUAGE_CODES_NAMESPACE = "apps"

    page_type = ChoicesCharField(_("page type"), max_length=100)
    app_namespace = models.CharField(
        ("app instance namespace"), max_length=100, blank=True, editable=False
    )

    class Meta:
        abstract = True

    @property
    def type(self):
        """
        Returns the appropriate page type instance, either the selected type or
        the first type in the list of ``TYPES`` if no type is selected or if
        the type does not exist anymore.
        """
        return self.TYPES_DICT.get(self.page_type, self.TYPES[0])

    @property
    def regions(self):
        return self.type.regions

    def save(self, *args, **kwargs):
        """
        Updates ``app_namespace``.
        """
        self.app_namespace = self.type.app_namespace(self)
        super().save(*args, **kwargs)

    save.alters_data = True

    def clean_fields(self, exclude=None):
        """
        Checks that required fields are given and that an app namespace only
        exists once per site and language.
        """
        exclude = [] if exclude is None else exclude
        super().clean_fields(exclude)

        type = self.type
        if type and type.get("required_fields"):
            missing = [
                field for field in type["required_fields"] if not getattr(self, field)
            ]
            if missing:
                error = _('This field is required for the page type "%s".') % (
                    self.get_page_type_display(),
                )
                errors = {}
                for field in missing:
                    if field in exclude:
                        errors.setdefault("__all__", []).append(f"{field}: {error}")
                    else:
                        errors[field] = error

                raise ValidationError(errors)

        if type and type.app_namespace(self):
            if self.__class__._default_manager.filter(
                Q(app_namespace=type.app_namespace(self)),
                Q(language_code=self.language_code),
                ~Q(pk=self.pk),
            ).exists():
                fields = ["__all__", "page_type"]
                fields.extend(type.get("required_fields", ()))
                raise ValidationError(
                    {
                        field: _("This exact app already exists.")
                        for field in fields
                        if field not in exclude
                    }
                )

    @staticmethod
    def fill_page_type_choices(sender, **kwargs):
        """
        Fills in the choices for ``page_type`` from the ``TYPES``
        class variable. This method is a receiver of Django's
        ``class_prepared`` signal.
        """
        if issubclass(sender, PageTypeMixin) and not sender._meta.abstract:
            field = sender._meta.get_field("page_type")
            field.choices = [(app.key, app.title) for app in sender.TYPES]
            field.default = sender.TYPES[0].key
            sender.TYPES_DICT = {app.key: app for app in sender.TYPES}
            global _APPS_MODEL
            _APPS_MODEL = sender

    @classmethod
    def check(cls, **kwargs):
        errors = super().check(**kwargs)
        errors.extend(cls._check_feincms3_appsmixin_templatemixin_clash(**kwargs))
        errors.extend(cls._check_feincms3_applications(**kwargs))
        return errors

    @classmethod
    def _check_feincms3_appsmixin_templatemixin_clash(cls, **kwargs):
        from feincms3.mixins import TemplateMixin

        if not cls._meta.abstract and issubclass(cls, TemplateMixin):
            return [
                Warning(
                    f"The model {cls._meta.label} extends both"
                    " PageTypeMixin and TemplateMixin. The new PageTypeMixin includes"
                    " the functionality of the TemplateMixin, please remove"
                    " the latter, fill in ``page_type`` fields either from"
                    " ``application`` (if non-empty) or from ``template_key``,"
                    " and rename ``app_instance_namespace`` to ``app_namespace``.",
                    obj=cls,
                    id="feincms3.W002",
                )
            ]
        return []

    @classmethod
    def _check_feincms3_applications(cls, **kwargs):
        for type in cls.TYPES:
            if isinstance(type, ApplicationType):
                try:
                    module = import_module(type.urlconf)
                except ModuleNotFoundError as exc:
                    yield Error(
                        f"The application type {type.key!r} has an unimportable"
                        f" URLconf value {type.urlconf!r}: {exc}",
                        obj=cls,
                        id="feincms3.E003",
                    )
                else:
                    app_name = getattr(module, "app_name", None)
                    if type.key != app_name and not getattr(
                        module, "ignore_app_name_mismatch", False
                    ):
                        yield Info(
                            f"The URLconf module of the application type {type.key!r}"
                            f" has app_name = {app_name!r} which doesn't match"
                            " the application key.",
                            obj=cls,
                            id="feincms3.I004",
                            hint=(
                                "Silence this warning by adding"
                                " 'ignore_app_name_mismatch = True'"
                                f" to the {type.urlconf!r} module if this is expected."
                            ),
                        ) def apps_urlconf(*, apps=None):
    """
    Generates a dynamic URLconf Python module including all application page
    types in their assigned place and adding the ``urlpatterns`` from
    ``ROOT_URLCONF`` at the end. Returns the value of ``ROOT_URLCONF`` directly
    if there are no active application page types.

    Since Django uses an LRU cache for URL resolvers, we try hard to only
    generate a changed URLconf when application URLs actually change.

    The application URLconfs are put in nested namespaces:

    - The outer application namespace is ``apps`` by default. This value can be
      overridden by setting the ``LANGUAGE_CODES_NAMESPACE`` class attribute of
      the page class to a different value. The instance namespaces consist of
      the ``LANGUAGE_CODES_NAMESPACE`` value with a language added at the end.
      As long as you're always using ``reverse_app`` you do not have to know
      the specifics.
    - The inner namespace is the app namespace, where the application
      namespace is defined by the app itself (assign ``app_name`` in the
      same module as ``urlpatterns``) and the instance namespace is defined
      by the application name (from ``TYPES``).

    Modules stay around as long as the Python (most of the time WSGI) process
    lives. Unloading modules is tricky and probably not worth it since the
    URLconf modules shouldn't gobble up much memory.

    The set of applications can be overridden by passing a list of
    ``(path, page_type, app_namespace, language_code)`` tuples.
    """

    if apps is None:
        apps = getattr(_apps_urlconf_cache, "cache", None)

        if apps is None:
            fields = ("path", "page_type", "app_namespace", "language_code")
            apps = list(
                _APPS_MODEL._default_manager.active()
                .with_tree_fields(False)
                .exclude(app_namespace="")
                .values_list(*fields)
                .order_by(*fields)
            )
            # NOTE! We *could* cache the module_name instead but we'd still
            # have to check if the module actually exists in the local Python
            # process.
            _apps_urlconf_cache.cache = apps

    if not apps:
        # No point wrapping ROOT_URLCONF if there are no additional URLs
        return settings.ROOT_URLCONF

    key = ",".join(itertools.chain.from_iterable(apps))
    module_name = "urlconf_%s" % hashlib.md5(key.encode("utf-8")).hexdigest()

    if module_name not in sys.modules:
        types = {app.key: app for app in _APPS_MODEL.TYPES if app.get("urlconf")}

        m = ModuleType(module_name)

        mapping = defaultdict(list)
        for path, page_type, app_namespace, language_code in apps:
            if page_type not in types:
                continue
            mapping[language_code].append(
                re_path(
                    r"^%s" % re.escape(path.lstrip("/")),
                    include(types[page_type]["urlconf"], namespace=app_namespace),
                )
            )

        m.urlpatterns = [
            re_path(
                r"",
                include(
                    (instances, _APPS_MODEL.LANGUAGE_CODES_NAMESPACE),
                    namespace="%s-%s"
                    % (_APPS_MODEL.LANGUAGE_CODES_NAMESPACE, language_code),
                ),
            )
            for language_code, instances in mapping.items()
        ]

        # Append patterns from ROOT_URLCONF instead of including them because
        # i18n_patterns only work in the root URLconf.
        urlconf = import_module(settings.ROOT_URLCONF)
        m.urlpatterns += urlconf.urlpatterns
        for attribute in ["handler400", "handler403", "handler404", "handler500"]:
            if hasattr(urlconf, attribute):
                setattr(m, attribute, getattr(urlconf, attribute))
        sys.modules[module_name] = m

    return module_name def reverse_any(
    viewnames,
    urlconf=None,
    args=None,
    kwargs=None,
    fallback=_sentinel,
    *fargs,
    **fkwargs,
):
    """
    Try reversing a list of viewnames with the same arguments, and returns the
    first result where no ``NoReverseMatch`` exception is raised. Return
    ``fallback`` if it is provided and all viewnames fail to be reversed.

    Usage:

    .. code-block:: python

        url = reverse_any(
            ("blog:article-detail", "articles:article-detail"),
            kwargs={"slug": "article-slug"},
        )
    """

    for viewname in viewnames:
        try:
            return reverse(viewname, urlconf, args, kwargs, *fargs, **fkwargs)
        except NoReverseMatch:
            pass
    if fallback is not _sentinel:
        return fallback
    raise NoReverseMatch(
        "Reverse for any of '%s' with arguments '%s' and keyword arguments"
        " '%s' not found." % ("', '".join(viewnames), args or [], kwargs or {})
    ) def reverse_app(namespaces, viewname, *args, languages=None, **kwargs):
    """
    Reverse app URLs, preferring the active language.

    ``reverse_app`` first generates a list of viewnames and passes them on
    to ``reverse_any``.

    Assuming that we're trying to reverse the URL of an article detail view,
    that the project is configured with german, english and french as available
    languages, french as active language and that the current article is a
    publication, the viewnames are:

    - ``apps-fr.publications.article-detail``
    - ``apps-fr.articles.article-detail``
    - ``apps-de.publications.article-detail``
    - ``apps-de.articles.article-detail``
    - ``apps-en.publications.article-detail``
    - ``apps-en.articles.article-detail``

    reverse_app tries harder returning an URL in the correct language than
    returning an URL for the correct instance namespace. The ``fallback``
    keyword argument is supported too.

    Example:

    .. code-block:: python

        url = reverse_app(
            ("category-1", "blog"),
            "post-detail",
            kwargs={"year": 2016, "slug": "my-cat"},
        )
    """

    if languages is None:
        current = get_language()
        languages = sorted(
            (row[0] for row in settings.LANGUAGES), key=lambda lang: lang != current
        )
    viewnames = [
        ":".join(r)
        for r in itertools.product(
            (
                f"{_APPS_MODEL.LANGUAGE_CODES_NAMESPACE}-{language}"
                for language in languages
            ),
            (namespaces if isinstance(namespaces, (list, tuple)) else (namespaces,)),
            (viewname,),
        )
    ]
    return reverse_any(viewnames, *args, **kwargs) def reverse_fallback(fallback, fn, *args, **kwargs):
    """
    Returns the result of ``fn(*args, **kwargs)``, or ``fallback`` if the
    former raises a ``NoReverseMatch`` exception. This is especially useful for
    reversing app URLs from outside the app and you do not want crashes if the
    app isn't available anywhere.

    The following two examples are equivalent, choose whichever you like best:

    .. code-block:: python

        reverse_fallback(
            "/",
            lambda: reverse_app(
                ("articles",),
                "article-detail",
                kwargs={"slug": self.slug},
            ),
        )

        reverse_fallback(
            "/",
            reverse_app
            ("articles",),
            "article-detail",
            kwargs={"slug": self.slug},
        )

    Note though that ``reverse_app`` supports directly specifying the fallback
    since 3.1.1:

    .. code-block:: python

        reverse_app(
            ("articles",),
            "article-detail",
            kwargs={"slug": self.slug},
            fallback="/",
        )
    """
    try:
        return fn(*args, **kwargs)
    except NoReverseMatch:
        return fallback class AbstractPage(TreeNode):
    """
    Short version: If you want to build a CMS with a hierarchical page
    structure, use this base class.

    It comes with the following fields:

    - ``parent``: (a nullable tree foreign key) and a ``position`` field for
      relatively ordering pages. While it is technically possible for
      ``position`` to be 0, e.g., data bulk imported from another CMS, it is not
      recommended, as the ``save()`` method will override values of 0 if you
      manipulate pages using the ORM.
    - ``is_active``: Boolean field. The ``save()`` method ensures that inactive
      pages never have any active descendants.
    - ``title`` and ``slug``
    - ``path``: The complete path to the page, starting and ending with a
      slash. The maximum length of path (1000) should be enough for everyone
      (tm, famous last words). This field also has a unique index, which means
      that MySQL with its low limit on unique indexes will not work with this
      base class. Sorry.
    - ``static_path``: A boolean which, when ``True``, allows you to fill in
      the ``path`` field all by yourself. By default, ``save()`` ensures that
      the ``path`` fields are always composed of a concatenation of the
      parent's ``path`` with the page's own ``slug`` (with slashes of course).
      This is especially useful for root pages (set ``path`` to ``/``) or,
      when building a multilingual site, for language root pages (i.e.
      ``/en/``, ``/de/``, ``/pt-br/`` etc.)
    """

    is_active = models.BooleanField(_("is active"), default=True)
    title = models.CharField(_("title"), max_length=200)
    slug = models.SlugField(_("slug"))
    position = models.PositiveIntegerField(
        db_index=True,
        editable=False,
        validators=[
            MinValueValidator(
                limit_value=1,
                message=_("Position is expected to be greater than zero."),
            )
        ],
    )

    # Who even cares about MySQL
    path = models.CharField(
        _("path"),
        max_length=1000,
        blank=True,
        unique=True,
        help_text=_(
            "Automatically generated by concatenating the parent's path and"
            " the slug unless the path is defined manually."
        ),
        validators=[
            RegexValidator(
                regex=r"^/(|.+/)$",
                message=_("Path must start and end with a slash (/)."),
            )
        ],
    )
    static_path = models.BooleanField(_("manually define the path"), default=False)

    objects = AbstractPageQuerySet.as_manager(with_tree_fields=True)

    class Meta:
        abstract = True
        ordering = ("position",)
        verbose_name = _("page")
        verbose_name_plural = _("pages")

    def __str__(self):
        return self.title

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Go through self.__dict__ to avoid triggering deferred field loading
        self._save_descendants_cache = (
            self.__dict__.get("is_active"),
            self.__dict__.get("path"),
        )

    def _branch_for_update(self):
        nodes = OrderedDict({self.pk: self})
        for node in self.descendants():
            # Assign already-updated instance:
            node.parent = nodes[node.parent_id]
            if not node.static_path:
                node.path = f"{node.parent.path}{node.slug}/"

            # Descendants of inactive nodes cannot be active themselves:
            if not node.parent.is_active:
                node.is_active = False
            nodes[node.id] = node
        return nodes

    def _path_clash_candidates(self):
        # Hook used in feincms3-sites
        return self.__class__._default_manager.exclude(
            Q(pk__in=self.descendants(), static_path=False) | Q(pk=self.pk)
        )

    def clean_fields(self, exclude=None):
        """
        Check for path uniqueness problems.
        """
        super().clean_fields(exclude)

        if self.static_path:
            if not self.path:
                raise validation_error(
                    _("Static paths cannot be empty. Did you mean '/'?"),
                    field="path",
                    exclude=exclude,
                )
        else:
            self.path = "{}{}/".format(
                self.parent.path if self.parent else "/", self.slug
            )

        super().clean()

        # Skip if we don't exist yet.
        if not self.pk:
            return

        clash_candidates = dict(self._path_clash_candidates().values_list("path", "id"))
        for pk, node in self._branch_for_update().items():
            if (
                node.path in clash_candidates
                and not clash_candidates[node.path] == node.pk
            ):
                raise validation_error(
                    _("The page %(page)s's new path %(path)s would not be unique.")
                    % {"page": node, "path": node.path},
                    field="path",
                    exclude=exclude,
                )

    def save(self, *args, **kwargs):
        """save(self, ..., save_descendants=None)
        Saves the page instance, and traverses all descendants to update their
        ``path`` fields and ensure that inactive pages (``is_active=False``)
        never have any descendants with ``is_active=True``.

        By default, descendants are only updated when any of ``is_active`` and
        ``path`` change. This can be overridden by either forcing updates using
        ``save_descendants=True`` or skipping them using
        ``save_descendants=False``.
        """
        save_descendants = kwargs.pop("save_descendants", None)

        if not self.static_path:
            self.path = "{}{}/".format(
                self.parent.path if self.parent else "/", self.slug
            )

        if not self.position:
            self.position = 10 + (
                self.__class__._default_manager.filter(parent_id=self.parent_id)
                .order_by()
                .aggregate(p=Max("position"))["p"]
                or 0
            )

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

        if save_descendants is True or (
            save_descendants is None
            and (self.is_active, self.path) != self._save_descendants_cache
        ):
            for pk, node in self._branch_for_update().items():
                if pk == self.pk:
                    continue
                node.save(save_descendants=False)

    save.alters_data = True

    def get_absolute_url(self):
        """
        Return the page's absolute URL

        If path is ``/``, reverses ``pages:root`` without any arguments,
        alternatively reverses ``pages:page`` with an argument of ``path``.
        Note that this ``path`` is not the same as ``self.path`` -- slashes
        are stripped from the beginning and the end of the string to make
        building an URLconf more straightforward.

        If any ``reverse()`` call fails it falls back to returning
        ``self.path`` prefixed with the script prefix which is ``/`` in the
        standard case.
        """
        try:
            if self.path == "/":
                return reverse("pages:root")
            return reverse("pages:page", kwargs={"path": self.path.strip("/")})
        except NoReverseMatch:
            return path_with_script_prefix(self.path)

    @classmethod
    def check(cls, **kwargs):
        errors = super().check(**kwargs)
        errors.extend(cls._check_feincms3_pages_default_ordering(**kwargs))
        return errors

    @classmethod
    def _check_feincms3_pages_default_ordering(cls, **kwargs):
        if not tuple(cls._meta.ordering) == ("position",):
            return [
                Warning(
                    "The page subclass isn't ordered by `position`.",
                    hint=(
                        'Define `ordering = ("position",)` when defining your own'
                        " `class Meta` on subclassed pages."
                    ),
                    obj=cls,
                    id="feincms3.W001",
                ),
            ]
        return [] class NoembedValidationForm(forms.ModelForm):
    """
    Tries fetching the oEmbed code for the given URL when cleaning form data

    This isn't active by default. If you want to validate URLs you should use
    the following snippet:

    .. code-block:: python

        from app.pages import models
        from feincms3 import plugins

        class SomeAdmin(...):
            inlines = [
                ...
                plugins.external.ExternalInline.create(
                    model=models.External,
                    form=plugins.external.NoembedValidationForm,
                ),
                ...
            ]
    """

    def clean(self):
        data = super().clean()
        url = data.get("url")
        if url and not oembed_html(url, cache_failures=False):
            raise forms.ValidationError(
                gettext("Unable to fetch HTML for this URL, sorry!")
            )
        return data def oembed_json(url, *, cache_failures=True, force_refresh=False, params=None):
    """
    Asks `Noembed <https://noembed.com/>`_ for the embedding HTML code for
    arbitrary URLs. Sites supported include YouTube, Vimeo, Twitter and many
    others.

    Successful embeds are always cached for 30 days.

    Failures are cached if ``cache_failures`` is ``True`` (the default). The
    durations are as follows:

    - Connection errors are cached 60 seconds with the hope that the connection
      failure is only transient.
    - HTTP errors codes and responses in an unexpected format (no JSON) are
      cached for 24 hours.

    The return value is always a dictionary, but it may be empty.
    """
    # Thundering herd problem etc...
    p = {"url": url, "nowrap": "on", "maxwidth": 1200, "maxheight": 800}
    if params:
        p.update(params)
    key = (
        "oembed-url-%s-data"
        % md5(json.dumps(p, sort_keys=True).encode("utf-8")).hexdigest()
    )

    data = None if force_refresh else cache.get(key)
    if data is not None:
        return data

    try:
        data = requests.get("https://noembed.com/embed", params=p, timeout=2).json()
    except (requests.ConnectionError, requests.ReadTimeout):
        # Connection failed? Hopefully temporary, try again soon.
        timeout = 60
    except (ValueError, requests.HTTPError):
        # Oof... HTTP error code, or no JSON? Try again tomorrow,
        # and we should really log this.
        timeout = 86400
    else:
        # Perfect, cache for 30 days
        cache.set(key, data, timeout=30 * 86400)
        return data

    if cache_failures:
        cache.set(key, {}, timeout=timeout)
    return {} class Regions:
    """
    ``Regions`` uses ``content_editor.contents.Contents`` and the
    ``feincms3.renderer.TemplatePluginRenderer`` to convert a list of plugins
    into a rendered representation, most often a HTML fragment.

    This class may also be instantiated directly but using the factory methods
    (starting with ``from_``) below is probably more comfortable.
    """

    @classmethod
    def from_contents(cls, contents, *, renderer, **kwargs):
        """
        Create and return a regions instance using the bare minimum of a
        contents instance and a renderer. Additional keyword arguments are
        forwarded to the regions constructor.
        """
        return cls(contents=contents, renderer=renderer, **kwargs)

    @classmethod
    def from_item(cls, item, *, renderer, inherit_from=None, timeout=None, **kwargs):
        """
        Create and return a regions instance for an item (for example a page,
        an article or anything else managed by django-content-editor).

        The item's plugins are determined by what is registered with the
        renderer. The plugin instances themselves are loaded lazily, and
        loading every time can be avoided completely by specifying a
        ``timeout``.
        """
        if timeout is not None and "cache_key" not in kwargs:
            key = f"{item._meta.label_lower}-{item.pk}"
            kwargs["cache_key"] = lambda region: f"{key}-{region}"
        return cls.from_contents(
            SimpleLazyObject(
                lambda: contents_for_item(
                    item, renderer.plugins(), inherit_from=inherit_from
                )
            ),
            renderer=renderer,
            timeout=timeout,
            **kwargs,
        )

    def __init__(self, *, contents, renderer, cache_key=None, timeout=None):
        self.contents = contents
        self.renderer = renderer
        self.cache_key = cache_key
        self.timeout = timeout
        self.handlers = {
            key[7:]: getattr(self, key)
            for key in dir(self)
            if key.startswith("handle_")
        }

    @property
    def regions(self):
        return self.contents.regions

    @cached_render
    def render(self, region, context=None):
        """
        Main function for rendering.

        Starts the generator and assembles all fragments into a safe HTML
        string.
        """
        return mark_safe("".join(self.generate(self.contents[region], context)))

    def generate(self, items, context):
        """
        Inspects all items in the region for a  ``subregion`` attribute and
        passes control to the subregions' respective rendering handler, named
        ``handle_<subregion>``. If ``subregion`` is not set or is falsy
        ``handle_default`` is invoked instead. This method raises a
        ``KeyError`` exception if no matching handler exists.

        You probably want to call this method when overriding ``render``.
        """
        items = deque(items)
        while items:
            subregion = getattr(items[0], "subregion", None) or "default"
            yield from self.handlers[subregion](items, context)

    def handle_default(self, items, context):
        """
        Renders items from the queue using the renderer instance as long as the
        items either have no ``subregion`` attribute or whose ``subregion``
        attribute is an empty string.
        """
        while True:
            yield self.renderer.render_plugin_in_context(items.popleft(), context)
            if not items or not matches(items[0], subregions={None, ""}):
                break class TemplatePluginRenderer(RegionRenderer):
    """
    TemplatePluginRenderer is deprecated, use
    :class:`~feincms3.renderer.RegionRenderer`.
    """

    def __init__(self, *args, **kwargs):
        warnings.warn(
            "TemplatePluginRenderer is deprecated. Switch to the RegionRenderer now.",
            DeprecationWarning,
            stacklevel=2,
        )
        super().__init__(*args, **kwargs) def render_list(
    request,
    queryset,
    context=None,
    *,
    model=None,
    paginate_by=None,
    template_name_suffix="_list"
):
    """
    Render a list of items

    Usage example::

        def article_list(request, ...):
            queryset = Article.objects.published()
            return render_list(
                request,
                queryset,
                paginate_by=10,
            )

    You can also pass an additional context dictionary and/or specify the
    template name suffix. The query parameter ``page`` is hardcoded for
    specifying the current page if using pagination.

    The queryset (or the page if using pagination) are passed into the template
    as ``object_list`` AND ``<model_name>_list``, i.e. ``article_list`` in the
    example above.
    """

    context = context or {}
    if paginate_by:
        object_list = Paginator(queryset, paginate_by).get_page(request.GET.get("page"))
    else:
        object_list = queryset

    model = model or queryset.model
    context.update(
        {"object_list": object_list, "%s_list" % model._meta.model_name: object_list}
    )
    return TemplateResponse(
        request, template_name(model, template_name_suffix), context
    ) @register.filter
def translations(iterable):
    """
    Return a list of dictionaries, one for each language in
    ``settings.LANGUAGES``. An example follows:

    .. code-block:: python

        [
            {"code": "en", "name": "English", "object": <instance>},
            {"code": "de", "name": "German", "object": None},
            # ...
        ]

    The filter accepts anything you throw at it. "It" should be an iterable of
    objects having a ``language_code`` property however, or anything
    non-iterable (such as ``None``). The filter always returns a list of all
    languages in ``settings.LANGUAGES`` but the ``object`` key's value will
    always be ``None`` if the data is unusable.
    """
    try:
        translations = {obj.language_code: obj for obj in iterable} if iterable else {}
    except TypeError:
        translations = {}

    return [
        {"code": code, "name": name, "object": translations.get(code)}
        for code, name in settings.LANGUAGES
    ] class HTML(html.HTML, PagePlugin):
    pass class Article(models.Model):
    title = models.CharField(_("title"), max_length=200)
    category = models.CharField(
        _("category"),
        max_length=100,
        choices=(("publications", "publications"), ("blog", "blog")),
    )

    class Meta:
        ordering = ["-pk"]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse_app(
            (self.category, "articles"), "article-detail", kwargs={"pk": self.pk}
        ) class External(external.External, PagePlugin):
    pass class Page(
    # Add this first so that AbstractPage.Meta's properties are active:
    AbstractPage,
    # For adding the articles app to pages through the CMS:
    PageTypeMixin,
    # We have a main and a footer navigation (meta):
    MenuMixin,
    # We're building a multilingual CMS. (Also, feincms3.applications depends on
    # LanguageMixin currently):
    LanguageAndTranslationOfMixin,
    # Allow redirecting pages to other pages and/or arbitrary URLs:
    RedirectMixin,
):

    # MenuMixin
    MENUS = [("main", _("main")), ("footer", _("footer"))]

    # PageTypeMixin. We have two templates and four apps.
    TYPES = [
        TemplateType(
            key="standard",
            title=_("standard"),
            template_name="pages/standard.html",
            regions=[Region(key="main", title=_("Main"))],
        ),
        TemplateType(
            key="with-sidebar",
            title=_("with sidebar"),
            template_name="pages/with-sidebar.html",
            regions=[
                Region(key="main", title=_("Main")),
                Region(key="sidebar", title=_("Sidebar")),
            ],
        ),
        ApplicationType(
            key="publications",
            title=_("publications"),
            urlconf="testapp.articles_urls",
        ),
        ApplicationType(
            key="blog",
            title=_("blog"),
            urlconf="testapp.articles_urls",
        ),
        ApplicationType(
            key="importable_module",
            title="importable_module",
            urlconf="importable_module",
            required_fields=("optional", "not_editable"),
        ),
        ApplicationType(
            key="translated-articles",
            title=_("translated articles"),
            urlconf="testapp.translated_articles_urls",
        ),
        ApplicationType(
            key="imprint",
            title="imprint",
            urlconf="feincms3.root.passthru",
            template_name="pages/standard.html",
            regions=[Region(key="main", title=_("Main"))],
        ),
    ]

    optional = models.IntegerField(blank=True, null=True)
    not_editable = models.IntegerField(blank=True, null=True, editable=False)

    class Meta(AbstractPage.Meta):
        unique_together = [("language_code", "translation_of")] class TranslatedArticle(LanguageAndTranslationOfMixin):
    title = models.CharField(_("title"), max_length=200)

    class Meta:
        ordering = ["-pk"]
        unique_together = [("language_code", "translation_of")]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        with override(self.language_code):
            return reverse_app("translated-articles", "detail", kwargs={"pk": self.pk})
from contextlib import contextmanager
from types import SimpleNamespace
from django.contrib.auth.models import User
from django.core.checks import Error, Warning
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.forms.models import modelform_factory
from django.template import Context, Template, TemplateSyntaxError
from django.test import Client, RequestFactory, TestCase
from django.test.utils import isolate_apps
from django.urls import NoReverseMatch, reverse, set_urlconf
from django.utils.translation import deactivate_all, override
from feincms3 import applications, mixins
from feincms3.applications import (
    ApplicationType,
    PageTypeMixin,
    apps_urlconf,
    reverse_any,
    reverse_app,
    reverse_fallback,
)
from feincms3.pages import AbstractPage
from feincms3.plugins.external import NoembedValidationForm, oembed_json
from feincms3.regions import Regions
from feincms3.renderer import TemplatePluginRenderer
from feincms3.shortcuts import render_list
from feincms3.templatetags.feincms3 import translations
from .models import HTML, Article, External, Page, TranslatedArticle
import requests_mock
                self.assertEqual(t.render(Context(ctx)).strip(), out)

            self.assertRaises(
                NoReverseMatch,
                Template("{% load feincms3 %}{% reverse_app 'a' 'a' 42 %}").render,
                Context(),
            )

    def test_reverse_app_failures(self):
        """Invalid parameters to {% reverse_app %}"""
        with self.assertRaises(TemplateSyntaxError) as cm:
            Template("{% load feincms3 %}{% reverse_app %}")
        self.assertEqual(
            str(cm.exception),
            "'reverse_app' takes at least two arguments, a namespace and a URL pattern name.",  # noqa
        )

    def test_descendant_update(self):
        """Saving pages with descendants updates descendants too"""
        self.prepare_for_move()
        root, p1, p2 = list(Page.objects.all())  # Fetch again, we need tree_* fields

        p2.static_path = True
        p2.save()

        with self.assertNumQueries(1):
            # Only update self
            root.save()

        p1.refresh_from_db()
        self.assertEqual(p1.path, "/root/p1/")

        with self.assertNumQueries(4):
            # Update self, fetch two descendants and save them
            root.save(save_descendants=True)

        root.slug = "blaaa"
        with self.assertNumQueries(4):
            # Update self, fetch two descendants and save them
            root.save(save_descendants=True)

        p1.refresh_from_db()
        self.assertEqual(p1.path, "/blaaa/p1/")

        p2.refresh_from_db()
        self.assertEqual(p2.path, "/root/p2/")

    def test_move_view_redirect(self):
        """Move view redirects as expected when encountering an invalid PK"""
        client = self.login()
        # move_view also redirects to index page when encountering invalid
        # object IDs
        response = client.get("/admin/testapp/page/asdf/move/")
        self.assertRedirects(response, "/admin/")

    def test_content_cloning(self):
        """Test that cloning the content works and replaces everything"""

        home_en = Page.objects.create(
            title="home",
            slug="home",
            path="/en/",
            static_path=True,
            language_code="en",
            is_active=True,
            menu="main",
        )

        home_en.testapp_snippet_set.create(
            template_name="snippet.html", ordering=10, region="main"
        )

        home_de = Page.objects.create(
            title="home",
            slug="home",
            path="/de/",
            static_path=True,
            language_code="de",
            is_active=True,
            menu="main",
        )

        home_de.testapp_snippet_set.create(
            template_name="snippet-33.html", ordering=10, region="main"
        )

        client = self.login()
        response = client.post(
            reverse("admin:testapp_page_clone", args=(home_en.pk,)),
            {"target": home_de.pk, "_set_content": True},
        )
        # print(response.content.decode("utf-8"))
        self.assertRedirects(
            response, reverse("admin:testapp_page_change", args=(home_de.pk,))
        )

        self.assertEqual(
            list(home_de.testapp_snippet_set.values_list("template_name", flat=True)),
            ["snippet.html"],
        )

    def test_default_type_fallback(self):
        """The PageTypeMixin falls back to the first template"""
        self.assertEqual(Page(page_type="__notexists").type.key, "standard")

    def test_apps_urlconf_no_apps(self):
        """apps_urlconf returns the ROOT_URLCONF when there are no apps at all"""
        self.assertEqual(apps_urlconf(apps=[]), "testapp.urls")

    def test_get_absolute_url(self):
        """Page.get_absolute_url with and without paths"""
        self.assertEqual(Page(path="/test/").get_absolute_url(), "/test/")
        self.assertEqual(Page(path="/").get_absolute_url(), "/")

    def test_render_list(self):
        """render_list, automatic template selection and pagination"""
        for i in range(7):
            Article.objects.create(title="Article %s" % i, category="publications")

        request = RequestFactory().get("/", data={"page": 2})

        response = render_list(