Applications (feincms3.applications)#

class feincms3.applications.ApplicationType(**kwargs)[source]#

Application page type

Example usage:

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.
        ),
    ]
class feincms3.applications.PageTypeMixin(*args, **kwargs)[source]#

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 LanguageMixin is a required dependency of feincms3.applications.

TYPES contains a list of page type instances, either TemplateType or 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"],
        ),
    ]
LANGUAGE_CODES_NAMESPACE = 'apps'#

Override this to set a different name for the outer namespace.

clean_fields(exclude=None)[source]#

Checks that required fields are given and that an app namespace only exists once per site and language.

static fill_page_type_choices(sender, **kwargs)[source]#

Fills in the choices for page_type from the TYPES class variable. This method is a receiver of Django’s class_prepared signal.

save(*args, **kwargs)[source]#

Updates app_namespace.

property type#

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.

class feincms3.applications.TemplateType(**kwargs)[source]#

Template page type

Example usage:

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",
            template_name="pages/standard.html",
            regions=[
                Region(key="main", title="Main"),
            ],

            # You may pass other arguments here, they will be available
            # on ``page.type`` as-is.
        ),
    ]
feincms3.applications.apps_middleware(get_response)[source]#

This middleware must be put in MIDDLEWARE; it simply assigns the return value of apps_urlconf() to request.urlconf. This middleware should probably be one of the first since it has to run before any resolving happens.

feincms3.applications.apps_urlconf(*, apps=None)[source]#

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.

feincms3.applications.page_for_app_request(request, *, queryset=None)[source]#

Returns the current page if we’re inside an app. Should only be called while processing app views. Will pass along exceptions caused by non-existing or duplicated apps (this should never happen inside an app because apps_urlconf() wouldn’t have added the app in the first place if a matching page wouldn’t exist, but still.)

Example:

def article_detail(request, slug):
    page = page_for_app_request(request)
    page.activate_language(request)
    instance = get_object_or_404(Article, slug=slug)
    return render(
        request,
        "articles/article_detail.html",
        {"article": article, "page": page},
    )

It is possible to override the queryset used to fetch a page instance. The default implementation simply uses the first concrete subclass of PageTypeMixin.

feincms3.applications.reverse_any(viewnames, urlconf=None, args=None, kwargs=None, fallback=<object object>, *fargs, **fkwargs)[source]#

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:

url = reverse_any(
    ("blog:article-detail", "articles:article-detail"),
    kwargs={"slug": "article-slug"},
)
feincms3.applications.reverse_app(namespaces, viewname, *args, languages=None, **kwargs)[source]#

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:

url = reverse_app(
    ("category-1", "blog"),
    "post-detail",
    kwargs={"year": 2016, "slug": "my-cat"},
)
feincms3.applications.reverse_fallback(fallback, fn, *args, **kwargs)[source]#

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:

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:

reverse_app(
    ("articles",),
    "article-detail",
    kwargs={"slug": self.slug},
    fallback="/",
)
feincms3.templatetags.feincms3.reverse_app(parser, token)[source]#

Reverse app URLs, preferring the active language.

Usage:

{% load feincms3 %}
{% reverse_app 'blog' 'detail' [args] [kw=args] [fallback='/'] %}

namespaces can either be a list or a comma-separated list of namespaces. NoReverseMatch exceptions can be avoided by providing a fallback as a keyword argument or by saving the result in a variable, similar to {% url 'view' as url %} does:

{% reverse_app 'newsletter' 'subscribe-form' fallback='/newsletter/' %}

Or:

{% reverse_app 'extranet' 'login' as login_url %}