Apps (feincms3.apps)

class feincms3.apps.AppsMixin(*args, **kwargs)[source]

The page class should inherit this mixin. It adds an application field containing the selected application, and an app_instance_namespace field which contains the instance namespace of the application. Most of the time these two fields will have the same value. This mixin also ensures that applications can only be activated on leaf nodes in the page tree. Note that currently the LanguageMixin is a required dependency of feincms3.apps.

APPLICATIONS contains a list of application configurations consisting of:

  • Application name (used as instance namespace)
  • User-visible name
  • Options dictionary

Available options include:

  • 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 AppsMixin.clean_fields.
  • app_instance_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 django.utils.translation import ugettext_lazy as _
from feincms3.apps import AppsMixin
from feincms3.mixins import LanguageMixin
from feincms3.pages import AbstractPage

class Page(AppsMixin, LanguageMixin, AbstractPage):
    APPLICATIONS = [
        ("publications", _("publications"), {
            "urlconf": "app.articles.urls",
        }),
        ("blog", _("blog"), {
            "urlconf": "app.articles.urls",
        }),
        ("contact", _("contact form"), {
            "urlconf": "app.forms.contact_urls",
        }),
        ("teams", _("teams"), {
            "urlconf": "app.teams.urls",
            "app_instance_namespace": lambda page: "%s-%s" % (
                page.application,
                page.team_id,
            ),
            "required_fields": ("team",),
        }),
    ]
LANGUAGE_CODES_NAMESPACE = 'apps'

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

application_config()[source]

Returns the selected application options dictionary, or None if no application is selected or the application does not exist anymore.

clean_fields(exclude=None)[source]

Checks that application nodes do not have any descendants, and that required fields for the selected application (if any) are filled out, and that app instances with the same instance namespace and same language only exist once on a site.

static fill_application_choices(sender, **kwargs)[source]

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

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

Updates app_instance_namespace.

feincms3.apps.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.apps.apps_urlconf(*, apps=None)[source]

Generates a dynamic URLconf Python module including all applications 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 applications.

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 APPLICATIONS).

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, application, app_instance_namespace, language_code) tuples.

feincms3.apps.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 AppsMixin.

feincms3.apps.reverse_any(viewnames, urlconf=None, args=None, kwargs=None, *fargs, **fkwargs)[source]

Tries reversing a list of viewnames with the same arguments, and returns the first result where no NoReverseMatch exception is raised.

Usage:

url = reverse_any((
    'blog:article-detail',
    'articles:article-detail',
), kwargs={'slug': 'article-slug'})
feincms3.apps.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.

Example:

url = reverse_app(
    ("category-1", "blog"),
    "post-detail",
    kwargs={"year": 2016, "slug": "my-cat"},
)
feincms3.apps.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},
)
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 %}